Java8揭秘(二)Java 8中的 Lambda表达式
2014-04-21 01:36
453 查看
第一章:Java8中的Lambda表达式
在这一章,我们说一说Lambda表达式的语法。我们将从经典的Java语法过渡到新式的Lambda表达式语法。我们也会讲一讲Lambda表达式的原理-即在运行时Lambda表达式如何表示,涉及哪些字节码指令。
入门
如果你熟悉Groovy或者Ruby这些支持Lambda表达式的编程语言,那么你一开始可能会认为Java的Lambda表达式并不像其他编程语言中的那样简洁。在java中,Lambda表达式是SAM类型,SAM类型是一个具有单一抽象方法的接口。(对了,Java8接口可以包含非抽象方法了-即default/defender方法,我们将在后面讲到它)举个例子,众所周知的Runnable接口就是SAM类型的:
Runnabler=()->System.out.println("helloLambda!"); |
Comparatorcmp=(x,y)->(x<y)?-1:((x>y)?1:0); |
Comparatorcmp=(x,y)->{ return(x<y)?-1:((x>y)?1:0); }; |
我来用下面的例子提示一下,使用Java8之前的语法,如何实现同样的比较器代码:
Comparatorcmp=newComparator(){ @Override publicintcompare(Integerx,Integer y){ return(x<y)? -1:((x>y)?1:0); } }; |
(x<y)?-1:((x> y)?1:0) |
看另外一个例子。如果我打算写一个方法,此方法接收一个Lambda表达式作为参数,那么该怎么写?嗯…你得把方法参数声明成函数接口,然后才能传递Lambda表达式进来,如下所示:
InterfaceAction{ voidrun(Stringparam); } publicvoidexecute(Actionaction){ action.run("Hello!"); } |
execute(newAction{ publicvoidrun(Stringparam){ System.out.println(param); } }); |
execute((Stringparam)->System.out.println(param)); |
execute(param->System.out.println(param)); |
既然这个Lambda表达式仅调用一个方法,且(该方法和函数接口中定义的方法)使用相同的参数,那么可以用方法引用(methodreference)替代这个Lambda表达式。如下所示:
execute(System.out::println); |
execute(s->System.out.println("*"+ s+"*")); |
函数接口
如刚才讲的,Lambda表达式在运行期表示为一个函数接口(functionalinterface)(或者说一个SAM类型),函数接口是一种只定义了一个抽象方法的接口。尽管JDK已经有一些接口都符合函数接口定义,比如Runnable和Comparator,但是这对API演进来说是显然不够的。我们又不能到处在代码里使用像Runnable这样的接口,因为这么做不合乎逻辑。JDK8中新增了一个包,java.util.function,这个包里有一些专门给新增的API使用的函数接口。此处就不列出所有的函数接口了,有兴趣可以自行学习下java.util.function:)
下面列出几个java.util.function中定义的接口,都非常有趣:
Consumer<T>–在T上执行一个操作,无返回结果
Supplier<T>–无输入参数,返回T的实例
Predicate<T>–输入参数为T的实例,返回boolean值
Function<T,R>–输入参数为T的实例,返回R的实例
java.util.function中新定义了超过40个函数接口。通常可以从接口的名字看出其含义。举个例子,BiFunction和上面提到的Function接口非常相似,只是唯一不同点是BiFunction有两个输入参数而Function有一个。
我们可以从那些新接口中看到另一个常见模式,该模式是在一个接口继承另一个接口的时候,把多个参数声明成同一种类型。例如,
@FunctionalInterfacepublicinterfaceBinaryOperatorextendsBiFunction<T,T,T>{} |
下面代码不能正常编译:
@FunctionalInterfaceinterfaceAction{ voidrun(Stringparam); voidstop(Stringparam); } |
java:Unexpected@FunctionalInterfaceannotation Actionisnotafunctionalinterface multiplenon-overridingabstractmethodsfoundininterfaceAction |
@FunctionalInterfaceinterfaceAction{ voidrun(Stringparam); defaultvoidstop(Stringparam){} } |
获取变量
如果Lambda表达式需要访问非静态变量或定义在其外部的对象,那么我们会碰到一种情况,就是Lambda表达式需要获取非体内变量,此时我们称之为一种“获取态”的Lambda表达式。思考下面比较器的例子:
intminus_one=-1; intone=1; intzero=0; Comparatorcmp=(x,y)->(x<y)?minus_one:((x>y)?one:zero); |
返回值是Lambda表达式
虽然在上面讲到的例子中,函数接口可以用作其他某个方法的参数,然而函数接口的用法并不限于当参数,函数接口还可以用作方法的返回值。也就是说我们可以从方法返回一个Lambda表达式,如下例子:publicclassComparatorFactory{ publicComparatormakeComparator(){ returnInteger::compareUnsigned; } } |
Comparatorcmp=newComparatorFactory().makeComparator(); cmp.compare(10,-5);//-1 |
序列化Lambda表达式
前一部分中使用的那段代码,创建了一个Comparator实例对象,该实例对象可以让客户端代码使用。所有工作看似相当成功。但是,有个严重的问题,即是如果我们尝试序列化那个Comparator实例对象,代码就会抛出因为序列化可能存在安全隐患,所以默认情况下,Lambda表达式不能序列化。为了能序列化,java8引入了所谓的类型关联(TypeIntersection),如下所示:
publicclassComparatorFactory{ publicComparatormakeComparator(){ return(Comparator&Serializable)Integer::compareUnsigned; } } |
使用类型关联的一般规则如下:
SAM&ZAM1&ZAM2&ZAM3
也就是说,如果返回结果是SAM类型的,那么我们可以用SAM类型和一个甚至多个ZAM类型“相关联”。我们现在事实上认为作为返回结果的Comparator实例对象也是Serializable类型的。
经过上面对返回结果强制转换类型后,编译器在编译后的class文件中多生成了一个方法,如下所示:
privatestaticjava.lang.Object$deserializeLambda$(java.lang.invoke.SerializedLambda); |
反编译Lambda表达式
现在给大家讲一讲这背后的实现原理。当我们在代码中使用Lambda表达式的时候,同时也了解下代码实际上是怎么编译的,这会很有趣。目前(像Java7之前的版本),如果你想在java中模仿Lambda表达式,那么你得定义一个匿名内部类。这样会在编译后生成一个相应的class文件。如果你在代码中定义多个匿名内部类,那么这些匿名类只不过是在其相应的class文件名字中增加一个数字后缀。Lambda表达式编译后会是怎样呢?
仔细思考下面的代码:
publicclassMain{ @FunctionalInterfaceinterfaceAction{ voidrun(Strings); } publicvoidaction(Actionaction){ action.run("Hello!"); } publicstaticvoidmain(String[]args){ newMain().action((Strings)->System.out.print("*"+s+"*")); } } |
$javap-pMain Warning:BinaryfileMaincontainscom.zt.Main Compiledfrom"Main.java" publicclasscom.zt.Main{ publiccom.zt.Main(); publicvoidaction(com.zt.Main$Action); publicstaticvoidmain(java.lang.String[]); privatestaticjava.lang.ObjectLambda$0(java.lang.String); } |
下面的main方法揭示了invokedynamic指令用来分派方法调用。
publicstaticvoidmain(java.lang.String[]); Code: 0:new#4//classcom/zt/Main 3:dup 4:invokespecial#5//Method"":()V 7:invokedynamic#6,0//InvokeDynamic#0:run:()Lcom/zt/Main$Action; 12:invokevirtual#7//Methodaction:(Lcom/zt/Main$Action;)V 15:return |
BootstrapMethods: 0:#40invokestaticjava/lang/invoke/LambdaMetafactory.metaFactory:(\ Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;\ Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;\ Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)\ Ljava/lang/invoke/CallSite; Methodarguments: #41invokeinterfacecom/zt/Main$Action.run:(Ljava/lang/String;)Ljava/lang/Object; #42invokestaticcom/zt/Main.Lambda$0:(Ljava/lang/String;)Ljava/lang/Object; #43(Ljava/lang/String;)Ljava/lang/Object; |
如果我定义一个名字是Lambda$0的静态方法会这么样?Lambda$0毕竟算一个有效标识符!于是,我定义了Lambda$0方法,如下所示:
PublicstaticvoidLambda$0(Strings){ return null; } |
java:thesymbolLambda$0(java.lang.String)conflictswitha compiler-synthesizedsymbolincom.zt.Main |
总结
在此为本文的第一章做一个小结。我敢肯定,Lambda表达式在不久的将来会对Java产生巨大的影响。又因为Lambda表达式语法结构相当不错,所以一旦开发者认识到像Lambda这些特性有益于提升开发效率,那么我们将会看到Lambda表达式更广泛的应用。相关文章推荐
- Java8揭秘(二)Java 8中的 Lambda表达式
- Java8 Lambda表达式教程
- Java 8 - Lambda表达式
- Java的lambda表达式实现解析
- 揭秘Java网络爬虫程序原理
- Java8 lambda表达式10个示例
- Java8新特性——Lambda表达式(二)Stream语法详解
- java8 lambda表达式
- java8-lambda表达式
- Java 9 揭秘(13. Collection API 更新)
- Java 1.8 新特性之(Lambda表达式)
- Java 9 揭秘(2. 模块化系统)
- java8 快速入门 lambda表达式
- 简记Java8_Lambda表达式
- Java8之lambda表达式
- JAVA 中Lambda表达式
- Java工厂设计模式-lambda表达式简单实例
- 谷歌掐架甲骨文:揭秘 Java 侵权案始末
- 浅析Java8新特性Lambda表达式和函数式接口
- java学习(lambda表达式)