在网络上包括许多书在介绍java8的时候都会提及lamda表达式与方法引用。
拿我们多线程经常用的Runnable接口来说:
public interface Runnable {
public abstract void run();
}
以前我们使用这类接口时,在一些场合为了方便会使用匿名内部类,比如下面这样:
Runnable object = new Runnable(){
@Override
public void run() {
System.out.printf("1");
}
};
这个时候很多人就说了,java你语法太重了,代码太繁琐了,那些类名和方法没必要写。于是,java8出了一个lambda
语法。之后,我们可以使用lambda
表达式来代替匿名内部类的方式。
Runnable object = () -> {
System.out.printf("1");
};
代码一下子就简洁了很多。
那方法引用是怎么回事呢?
方法引用通过方法的名字来指向一个方法。
方法引用使用一对冒号 ::
例如我们熟悉的 System.out.println(); 它的方法引用就是 System.out::println
在一般的介绍中,方法引用会等价于lambda
。比如
Runnable object = System.out::println;
等价于
Runnable object = ()->{
System.out.println();
};
如果我们的实现只使用到了某一个类的静态方法,那么用方法引用比lamda要更加简洁。但是在某些情况下,使用方法引用会造成capturing lambda(lamda函数的捕获现象)。在大多数时候没影响,在循环的情况下会造成不必要的垃圾回收。
进入正文:
lambda
函数的捕获现象
如下所示,同样效果的两个函数,前者会造成lamda函数的捕获现象。
Runnable createLambdaWithCapture() {
return System.out::println;
}
Runnable createLambdaWithApparentCapture() {
return () -> System.out.println();
}
实际上,第一个函数等价于:
Runnable createLambdaWithCapture() {
PrintWriter foo = System.out;
return () -> foo.println(); // foo is captured and effectively final
}
与后者相比,多了一个foo引用。
为什么方法引用翻译会多一个引用呢?
这涉及到了编译原理。类似于a = b + c +d时,生成的中间代码是 $1 = $2 + $3 , $4 = $1 + $5;最终$4是我们要计算的a变量的值,$1是中间变量(编译原理已经学太久啦,只能说个大概是这样)。上面引用foo就是编译过程中生成的中间变量
另外我们看StackOverflow上的一个回答,是这样说的:
The method reference
System.out::println
will evaluateSystem.out
first, then create the equivalent of a lambda expression which captures the evaluated value. Usually, you would useo -> System.out.println(o)
to achieve the same as the method reference, but this lambda expression will evaluateSystem.out
each time the method will be called.
方法引用 System.out::println将会首先分析System.out,然后再创建一个等价的lambda表达式,那个表达式会有一个捕获值(也就是我们上文代码中多出来的引用foo)。当你使用的是o -> System.out.println(o)这种lambda表达式时(与System.out::println方法引用相同),System.out会在每次方法真正被调用的时候都分析System.out。
回到我们之前的代码
Runnable createLambdaWithCapture() {
return System.out::println;
}
Runnable createLambdaWithApparentCapture() {
return () -> System.out.println();
}
将方法引用替换成等价lambda之后:
Runnable createLambdaWithCapture() {
PrintWriter foo = System.out;
return () -> foo.println(); // foo is captured and effectively final
}
前后两者的区别在于前者在方法真正被调用之前就已经计算出System.out部分,之后就直接调用foo.println();而后者则在每次方法调用时都会计算System.out。