目录 [−]  Java 8发布有一段日子, 大家关注Java 8中的lambda可能更早, 对Java 8中这一最重要的语言变化也基本熟悉了。这篇文章将深入研究Java 8中的lambda特性以及Stream接口等, 讨论一些深层次的技术细节。
Java Lambda语法 尽管你已经很熟悉了, 我们还是先回顾一下lambda表达式的语法。
“A lambda expression is like a method: it provides a list of formal parameters and a body—an expression or block—expressed in terms of those parameters,”JSR 335 
1
Function<Integer, Integer> fun = (Integer x, Integer y) -> {return  x + y;}
如果body只有一个表达式,可以省略body的·大括号 和 return。
1
Function<Integer, Integer> fun = (Integer x, Integer y) -> x + y
参数可以声明类型,也可以根据类型推断而省略。
1
Function<Integer, Integer> fun = (x, y) -> x + y
但是不能部分省略。
1
Function<Integer, Integer> fun = (x, Integer y) -> x + y 
单个的参数可以省略括号。
1
2
Function<Integer, Integer> fun = (x) -> x+1 
Function<Integer, Integer> fun = x -> x+1 
但是不能加上类型声明。
1
Function<Integer, Integer> fun = Integer x -> x+1  
如果没有参数, 括号是必须的。
1
2
() -> 1995 
() -> { System.gc(); }
Java Lambda递归 匿名函数是没有名字的, 但是Lambda表达式可以赋值给一个变量或者作为参数传递, 这意味着它有"名字"。 那么可以利用这个名字进行递归吗?lambdafaq 网站说可以。
1
2
Function<Long, Long> fib = x -> {if  (x ==1  || x == 2 ) return  1 L; else  return  fib.apply(x -1 ) + x;};
System.out.println(fib.apply(3 L));
实际你并不能编译这段代码, 因为编译器认为fib可能没有初始化。
1
The local variable fib may not have been initialized
没办法递归了吗?
1
2
IntToDoubleFunction[] foo = { null  };
foo[0 ] = x -> { return   ( x == 0 )?1 :x* foo[0 ].applyAsDouble(x-1 );};
或者 (泛型数组的创建有些麻烦)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
   @SuppressWarnings ("unchecked" )
private  static  <E> E[] newArray (Class clazz, int  size)
{
    return  (E[]) Array.newInstance(clazz, size);  
}
public  static  void  main (String[] args) throws  InstantiationException, IllegalAccessException, SecurityException, NoSuchMethodException {
	
	
	Function<Long, Long>[] funs = newArray(Function.class, 1 );
	funs[0 ] = x -> {if  (x ==1  || x == 2 ) return  1 L; else  return  funs[0 ].apply(x -1 ) + x;};
	System.out.println(funs[0 ].apply(10 L));
}
或者使用一个helper类。
1
2
BiFunction<BiFunction, Long, Long> factHelper = (f, x) -> {if  (x ==1  || x == 2 ) return  1 L; else  return  x + (long )f.apply(f,x-1 );};
Function<Long, Long> fib = x -> factHelper.apply(factHelper, x);
捕获变量 就像本地类和匿名类一样, Lambda表达式可以捕获变量(capture variable)。
In addition, a local class has access to local variables. However, a local class can only access local variables that are declared final. When a local class accesses a local variable or parameter of the enclosing block, it captures that variable or parameter
但是Lambda表达式不强迫你将变量声明为final, 只要它的行为和final 变量一样即可,也就是等价final.s不必声明为final,实际加上final也不会编译出错。
1
2
3
String s = "smallnest" ;
Runnable r = () -> System.out.println("hello "  + s);
r.run();
但是下面的例子s实际已经不是final了,编译会出错。,
1
2
3
4
String s = "smallnest" ;
Runnable r = () -> System.out.println("hello "  + s);
s = "colobu" ;
r.run();
下面的代码一样也会编译不成功:
1
2
3
String s = "smallnest" ;
Runnable r = () -> {s = "abc" ; System.out.println("hello "  + s);};
r.run();
注意final仅仅是变量不能再被赋值, 而变量字段的值是可以改变的。
1
2
3
4
5
Sample s = new  Sample();
s.setStr("smallnest" );
Runnable r = () -> System.out.println("hello "  + s.getStr());
s.setStr("colobu" );
r.run();
这里我们可以更改s的str字段的值。
序列化 一般序列化/反序列化 Lambda表达式可以被序列化。下面是一个简单的例子。
1
2
3
4
5
6
7
8
9
10
Runnable r = (Runnable & Serializable)() -> System.out.println("hello serialization" );
FileOutputStream fos = new  FileOutputStream("Runnable.lambda" );
ObjectOutputStream os = new  ObjectOutputStream(fos);
os.writeObject(r);
FileInputStream fis = new  FileInputStream("Runnable.lambda" );
ObjectInputStream is = new  ObjectInputStream(fis);
r = (Runnable) is.readObject();
r.run();
注意(Runnable & Serializable)是Java 8中新的语法。 cast an object to an intersection of types by adding multiple bounds. 
带捕获变量的序列化/序列化 再看一个带captured argument的例子。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
	class Sample implements Serializable {
	private  String str;
	public  String getStr () {
		return  str;
	}
	public  void  setStr (String str) {
		this .str = str;
	}
}
	public  static  void  serializeLambda () throws  Exception {
		Sample s = new  Sample();
		s.setStr("smallnest" );
		SampleSerializableInterface r = () -> System.out.println("hello "  + s.getStr());
		FileOutputStream fos = new  FileOutputStream("Runnable.lambda" );
		ObjectOutputStream os = new  ObjectOutputStream(fos);
		os.writeObject(r);
		s.setStr("colobu" );
	}
	public  static  void  deserializeLambda () throws  Exception {
		FileInputStream fis = new  FileInputStream("Runnable.lambda" );
		ObjectInputStream is = new  ObjectInputStream(fis);
		SampleSerializableInterface r = (SampleSerializableInterface) is.readObject();
		r.run();
	}
可以看到连同captured argument s一同序列化了。 即使反序列化出来,captured argument也不是原来的s了。hello smallnest。
方法引用 方法引用是一个有趣的特性, 方法类似指针一样可以被直接引用。 新的操作符"::"用来引用类或者实例的方法。
static method reference 1
2
3
4
BiFunction<Long,Long,Integer> bf = Long::compare;
Long a = 10 L;
Long b = 11 L;
System.out.println(bf.apply(a, b));
instance method reference 1
2
Consumer<String> c = System.out::println;
c.accept("hello colobu" );
以上两种情况引用的方法签名应和 target type的方法签名一样,方法的名字不一定相同。
arbitrary instance reference 1
2
String[] stringArray = { "Barbara" , "James" , "Mary" , "John" , "Patricia" , "Robert" , "Michael" , "Linda"  };
Arrays.sort(stringArray, String::compareToIgnoreCase);
这是一个很有趣的使用方法。 可以引用任意的一个类型的实例。等价的lambda表达式的参数列表为(String a, String b),方法引用会调用a.compareToIgnoreCase(b)。
constructor reference 另一种特殊的方法引用是对构造函数的引用。new。 一个类有多个构造函数, 会根据target type选择最合适的构造函数。
多继承 由于Java 8引入了缺省方法(default method)的特性,Java也想其它语言如C++一样遇到了多继承的问题。这里列出两个典型的多继承的情况。
三角继承 三角继承如下图所示。
1
2
3
4
5
6
7
8
9
10
	class A {
	public  A  () {
		System.out.println("A()" );
	}
	
	public  A (int  x) {
		System.out.println("A(int x)" );
	}
}
菱形继承 菱形继承如下图所示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
interface A {	 
	 default  void  say() {
		 System.out.println("A says" );
	 }
}
interface B extends A{
	 
	 default  void  say() {
		 System.out.println("B says" );
	 }
}
interface C extends A{
	 
	 default  void  say() {
		 System.out.println("C says" );
	 }
}
class D implements A, B, C{
	 	 
}
直接编译出错。原因是Duplicate default methods.D中重载say方法, 自定义或者使用父类/接口的方法。 注意其写法接口.super.default_method_name
1
2
3
4
5
	class D implements A, B, C{
	public  void  say () {
		 B.super .say();
	}	 
}
叉型继承 叉型继承如下图所示
B C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
interface B {	 
	 default  void  say() {
		 System.out.println("B says" );
	 }
}
class C {
	 
	 public  void  say () {
		 System.out.println("C says" );
	 }
}
class D extends C implements  B{
	  
}
上面的代码输出C says。
原则: 
类优先于接口。 如果一个子类继承的父类和接口有相同的方法实现。 那么接口中的定义会被忽略。 如第三个例子。 子类型中的方法优先于府类型中的方法。 如第一个例子。 如果以上条件都不满足, 如第二个例子,则必须显示覆盖/实现其方法,或者声明成abstract。 Stream接口的lazy方法和eager方法 新增加的Stream接口有很多方法。
lazy例子:
1
allStudents.stream().filter(s -> as.age> 16 );
filter并不会马上对列表进行遍历筛选, 它只是为stream加上一些"秘方"。当前它的方法实现不会被执行。直到遇到eager类型的方法它才会执行。
eager例子:
1
allStudents.stream().filter(s -> as.age> 16 ).count();
原则: 
Functional interface只能有一个方法吗? Functional interface又被称作Single Abstract Method (SAM)或者Role Interface 。
1
2
3
4
5
interface MyI {
	void  apply(int  i);
	
	String toString();
}
MyI声明了两个方法apply和toString()。 它能作为一个lambda 表达式的target type吗?
1
2
MyI m = x -> System.out.println(x);	
m.apply(10 );
没问题, 代码可以正常编译, 程序正常运行。
同样,interface Foo { boolean equals(Object obj); }也不是一个functional interface,因为没有声明一个方法。` java
Lambda的性能 Oracle公司的性能工程师Sergey Kuksenko有一篇很好的性能比较的文档: JDK 8: Lambda Performance study , 详细而全面的比较了lambda表达式和匿名函数之间的性能差别。这里是视频 。 16页讲到最差(capture)也和inner class一样, non-capture好的情况是inner class的5倍。
lambda开发组也有一篇ppt , 其中也讲到了lambda的性能(包括capture和非capture的情况)。看起来lambda最差的情况性能内部类一样, 好的情况会更好。
Java 8 Lambdas - they are fast, very fast 也有篇文章 (可能需要翻墙),表明lambda表达式也一样快。