http://unmi.cc/jdk8-lambda-method-references/
Lambda 允许我们定义匿名方法(即那个 Lambda 表达式,或叫闭包),作为一个功能性接口的实例。如果你不想把一个 Lambda 表达式写得过大,那么你可以把表达式的内容分离出来写在一个方法中,然后在放置 Lambda 表达式的位置上填上对那个方法的引用。
方法引用也应看作是一个 Lambda 表达式,所以它也需要一个明确的目标类型充当功能性接口的实例。简单说就是被引用的方法要与功能接口的 SAM(Single Abstract Method) 参数、返回类型相匹配。方法引用的引入避免了 Lambda 写复杂了可读性的问题,也使得逻辑更清晰。
为了应对方法引用这一概念, JDK8 又重新借用了 C++ 的那个 “::” 域操作符,全称为作用域解析操作符。
上面的表述也许不好明白,我看官方的那份 State of the Lambda 也觉得不怎么容易理解,特别是它举了那个例子很难让人望文生意。我用个自己写的例子来说明一下吧。
目前的 Eclipse-JDK8 版还不能支持方法引用的特性,幸好就是在昨天正式版的 NetBeans IDE 7.4 对 JDK8 有了较好的支持,所以在 NetBeans 7.4 中写测试代码。
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | package testjdk8;
/**
*
* @author Unmi
*/
public class TestJdk8 {
public static void main(String[] args) {
//使用 Lambda 表达式,输出: 16: send email
start((id, task) -> id + ": " + task);
//或者
Machine m1 = (id, task) -> id + ": " + task;
m1.doSomething( 16 , "send email" );
//使用方法引用,输出: Hello 16: send email
start(TestJdk8::hello);
//或者
Machine m2 = TestJdk8::hello;
m2.doSomething( 16 , "send email" );
}
private static void start(Machine machine){
String result = machine.doSomething( 16 , "send email" );
System.out.println(result);
}
public static String hello( int id, String task){
return "Hello " + id + ": " + task;
}
}
@FunctionalInterface
interface Machine {
public String doSomething( int id, String task);
}
|
说明:
1. Machine 是一个功能性接口,它只有一个抽象方法
2. start(Machine machine) 方法为 Lambda 表达式提供了一个上下文,表明它期盼接收一个 Machine 的功能性接口类型
3. start((id, task) -> id + ": " + task), 是传递了一个 Lambda 表达式给 start() 方法
4. start(TestJdk8::hello) 是把指向 TestJdk8::hello 方法的引用传递给了 start() 方法,这里可以理解 hello() 方法是 Lambda 表达式的另一种表现形式。
对应一下两个 start() 方法调用的参数,Lambda 表达式的参数列表 (id, task) 与 hello 方法的参数 (int id, String task) 是一致的,返回值类型也是一致的。
想像一下如果一个 Lambda 表达式的代码量很大,全部挤在一起作为 start() 方法的参数部分,混乱也不太方便于单步调试。所以可以把 Lambda 的实现挪出来放在一个单独的方法中,在使用处只放置一个对该方法的引用即可。借助于方法引用,JDK8 把方法与 Lambda 表达式巧妙的结合了起来,直接的说 Lambda 表达就是一个方法,它用自己的方法列表和返回值。
那么符合什么条件的方法可以作为 Lambda 表达式来用呢?答:方法签名与功能性接口的 SAM 一致即可。比如,可以进行下面的赋值:
1 2 | Consumer<Integer> b1 = System::exit //void exit(int status) 与 Consumer 的 SAM void accept(T t) 相匹配
Runnable r = MyProgram::main; //void main(String... args) 与 run() 方法能配上对
|
有些什么样子的方法引用:
- 静态方法 (ClassName::methName)
- 对象的实例方法 (instanceRef::methName)
- 对象的super 方法 (super::methName)
- 类型的实例方法 (ClassName::methName, 引用时和静态方法是一样的,但这里的 methName 是个实例方法)
- 类的构造方法 (ClassName::new)
- 数组的构造方法 (TypeName[]::new)
第 1 条,静态方法以 ClassName 为作用域好理解,第 4 条中实例方法也可以用 ClassName::methName 的方式去引用,那么这里又有个约定了:如果实例方法用类型来引用的时候,那么调用时第一个参数将作为该引用方法的接收者,其余参数依次作为引用方法的参 数。举个例子:
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | package testjdk8;
import java.util.function.Function;
/**
*
* @author Unmi
*/
public class TestJdk8 {
public static void main(String[] args) {
Function<String, String> upperfier = String::toUpperCase;
System.out.println(upperfier.apply( "Hello" )); //HELLO
Machine m = TestJdk8::hello; //hello 是实例方法
TestJdk8 test = new TestJdk8();
//test 作为 hello 方法的接收者,"Unmi" 作为 task 参数
System.out.println(m.doSomething(test, "Unmi" )); //Hello Unmi
}
public String hello(String task){
return "Hello " + task;
}
}
@FunctionalInterface
interface Machine {
public String doSomething(TestJdk8 test, String task);
}
|
上面的代码应该能有助于理解实例方法用类型来引用,如果引用的是泛型方法,类型写在 :: 之前。
同样当然对于第 2 条,引用实例方法时,SAM 的第一个参数也作为接收者,其作参数依次填充过去。
第 5 条,类的构造方法要用类型去引用,new 相当一个返回当前类型实例的实例方法,所以
1 2 | SocketImplFactory factory = MySocketImpl:: new ;
SocketImpl socketImpl = factory.createSocketImpl();
|
数组是种类型,可以认为数组的构造方法是只接受一个整形参数,所以能这样引用数组的构造方法:
1 2 | IntFunction< int []> arrayMaker = int []:: new ;
int [] array = arrayMaker.apply( 10 ); // creates an int[10]
|
小结:Lambda 表达式就是一个功能性接口的实例,因而调用方式参照功能性接口。Lambda 表达式可抽取到一个方法中,然后用方法引用指向这个方法,被引用的方法签名与功能性接口的 SAM 的一致性,注意引用实例方法时,SAM 的第一个参数将作为引用方法的接收者。我们把数组理解为有一个接收整数的构造方法。
posted on 2014-12-25 16:08
SIMONE 阅读(2382)
评论(0) 编辑 收藏 所属分类:
JAVA