首页 > 编程语言 > 详细

JAVA8 十大新特性详解

时间:2015-02-04 02:09:00      阅读:412      评论:0      收藏:0      [点我收藏+]

一、lambda含义

? ? lambda表示数学符号“λ”,计算机领域中λ代表“λ演算”,表达了计算机中最基本的概念:“调用”和“置换”。在很多动态语言和C#中都有相应的lambda语法,这类语法都为了简化代码,提高运行效率。

?

二、lambda 项目的背景参考这里

? ??无论是面向对象语言还是函数式语言,基本数值都可以被动态的封装入程序动作:面向对象语言通过“方法”,函数式语言通过“函数。

? ? 介于“方法”和“函数”的定义有很多种,补充下IBM知识库的解释

在面向对象语言中,方法不是一阶值(First-class value),在函数式语言中,函数是一阶值。在函数式语言中,函数可以作为另一个函数的返回值或参数,还可以作为一个变量的值,函数可以嵌套定义,而在面向对象语言中的的“方法”做不到这点。

? ? Java可以说是面向对象语言的代表,如果要调用其方法,需要先创建对象。不过Java对象都是“重量级”的,实例化具体的类的对象,需要经过定义和申明两个阶段。比如定义方法,并给内部字段赋初始值。但是一个对象只包含一个方法的情况很多,比如实现API中的“回调接口”功能的类,在swing中有接口:

?

Java代码??bubuko.com,布布扣
  1. <span><span><span?style="">public?interface?ActionListener?{???
  2. ????void?actionPerformed(ActionEvent?e);??
  3. }</span></span></span>??

?

? ?现有的实现方式大多是:

?

Java代码??bubuko.com,布布扣
  1. <span><span><span?style="">button.addActionListener(new?ActionListener()?{???
  2. ??public?void?actionPerformed(ActionEvent?e)?{???
  3. ????ui.dazzle(e.getModifiers());??
  4. ??}??
  5. });</span></span></span>??

?

? ? 很多现有的类库都基于这种设计实现,所以对于代码被明确定义运行在单独线程的API来说,匿名内部类尤为重要。这些匿名内部类只存在于创建它的线程中。但是在并行计算领域,CPU的制造商着力发展多核技术来提升CPU的功能,这么做几乎无法依靠多核的优势来提升其性能。

?

?

? ??鉴于回调函数和其他功能式语法的关系越来越密切,所以必须建立尽可能的轻量级的数据模型(从编码角度而言,性能方面下文再说)。对于这点来说匿名内部类的缺点如下:

1.语法相对复杂。

2.在调用内部类的上下文中,指引和this的指代容易混淆。

3.类加载和实例创建语法不可避免。

4.不能引用外部的非final对象。

5.不能抽象化控制流程

?

?

? ? 针对这些问题,lambda项目致力于

1. 消除问题1和问题2,通过引入更简单的表达式和局部变量的定义规则。

2. 回避问题3,定义更灵活更友善的语法。这里只是回避,类加载和实例化本身不可避免。下文会解释。

3. 改善问题4,允许用户使用最终有效的局部变量。

?

? ? 不过lambda项目目前并不能解决所有关于内部类的问题。问题4和问题5没有完全解决,这计划在将类版本中继续改善。对于性能方面,原文也没有提,不过后面有些补充。

?

?

三、lambda用法

? ??通过上文可以了解到,lambda语法是针对“回调接口”和“匿名内部类”作出的改进,所以lambda的语法目前仅对于部分接口,这些接口的特点是只含一个抽象方法,在lambda项目中,早期称为SAM类型(SAM = single abstract method 即单一抽象方法)。在最新的文档中(即这个版本),它们有了新名字,叫函数接口(functional interface),比如:

?
1java.lang.Runnable
2java.util.concurrent.Callable
3java.security.PrivilegedAction
4java.util.Comparator
5java.io.FileFilter
6java.nio.file.PathMatcher
7java.lang.reflect.InvocationHandler
8java.beans.PropertyChangeListener
9java.awt.event.ActionListener
10javax.swing.event.ChangeListener
?
lambda的语法包括三部分
1、参数列表
2、箭头符号"->"
3、代码块。

?

?

? ? 其中代码块很像一个方法体,return语句将控制权交还给匿名方法(anonymous method,即lambda表达式)的调用者;break和continue不能出现在函数体的顶部,不过可以出现在内部的循环里;如果代码块得出最终结果,那么每一个控制路径(control path) 必须都有返回或抛出异常。

如果代码块只有简单一行,可以省略return关键字和“{}”符号(以下所写的例子都是基于JDK 1.8 lambda预览版),比如:

?

?

Java代码??bubuko.com,布布扣
  1. <span><span><span?style="">public?class?LambdaTest?{??????
  2. ????public?static?void?main(String...?args)?{??
  3. ????????//这里有{}和return?以及?;??
  4. ????????Runnable?r?=?()?->?{?System.out.println("hello?world");?};??
  5. ??????????
  6. ????????//这里不需要{}和return??
  7. ????????java.util.Comparator<String>?c?=?(String?s1,?String?s2)?->?s2.length()-s1.length();??????????
  8. ????????r.run();??
  9. ????????System.out.println(c.compare("s1",?"12323"));??
  10. ????}??
  11. }</span></span></span>??

?

?

输出为:

hello world

3

?

?

除了这些现有接口,我们还可以自定义函数接口:

?

Java代码??bubuko.com,布布扣
  1. <span><span><span?style="">public?class?LambdaTest?{??
  2. ????interface?lambdaInterface?{??
  3. ????????public?void?me(String?str);??
  4. ????}??
  5. ??
  6. ????public?static?void?main(String...?args)?{??
  7. ????????lambdaInterface?li?=?(String?s)->{System.out.println(s);};??
  8. ????????li.me("hello?world!");??
  9. ????}??
  10. }</span></span></span>??

?输出为:

?

hello world!

?

? ? 新的lambda方法从语法上的确是简化了很多。和lambda第一次发布的语法相比也优雅很多。

?

?

四、lambda代码块的字节码

?

看完了语法的确很简单,那么lambda是怎么实现的,就得从字节码考察了。这里和匿名内部类做个对比,编译如下代码:

?

Java代码??bubuko.com,布布扣
  1. <span><span><span?style="">public?class?LambdaTest?{??
  2. ????lambdaInterface?li1?=?()->{System.out.println(this);};??
  3. ????lambdaInterface?li2?=?new?lambdaInterface(){??
  4. ????????????public?void?me(){??
  5. ????????????????System.out.println(this);??
  6. ????????????}??
  7. ????????};??
  8. ??????
  9. ????public?static?void?main(String...?args)?{??
  10. ????????LambdaTest?lt?=?new?LambdaTest();??
  11. ????????lt.li1.me();??
  12. ????????lt.li2.me();??
  13. ????}??
  14. }??
  15. ??
  16. interface?lambdaInterface?{??
  17. ????public?void?me();??
  18. }</span></span></span>??

编译后有会有四个文件:

?

LambdaTest.class ? ? ? ? ?

LambdaTest$1.class ? ? ?

LambdaTest$2.class

lambdaInterface.class ? ?

?

?

它的的输出结果为:

LambdaTest@200bde

LambdaTest$1@1eb41d6

结果很明显地显示,lambda代码块和内部类的this的指引是不一样的。lambda代码块输出的是调用者的this,即lambdaTest.class的实例。匿名内部类输出的是自己lambdaTest$1.class的实例。

?

?

先看看lambdaTest.class的字节码:

?

Txt代码??bubuko.com,布布扣
  1. <span><span><span?style="">public?class?LambdaTest?{??
  2. ??lambdaInterface?li1;??
  3. ??
  4. ??lambdaInterface?li2;??
  5. ??
  6. ??public?LambdaTest();??
  7. ????Code:??
  8. ???????0:?aload_0??
  9. ???????1:?invokespecial?#1??????????????????//?Method?java/lang/Object."<init>":()V??
  10. ???????4:?aload_0??
  11. ???????5:?new???????????#2??????????????????//?class?LambdaTest$2??
  12. ???????8:?dup??
  13. ???????9:?aload_0??
  14. ??????10:?aload_0??
  15. ??????11:?invokespecial?#3??????????????????//?Method?LambdaTest$2."<init>":(LLambdaTest;LLambdaTest;)V??
  16. ??????14:?putfield??????#4??????????????????//?Field?li1:LlambdaInterface;??
  17. ??????17:?aload_0??
  18. ??????18:?new???????????#5??????????????????//?class?LambdaTest$1??
  19. ??????21:?dup??
  20. ??????22:?aload_0??
  21. ??????23:?invokespecial?#6??????????????????//?Method?LambdaTest$1."<init>":(LLambdaTest;)V??
  22. ??????26:?putfield??????#7??????????????????//?Field?li2:LlambdaInterface;??
  23. ??????29:?return??
  24. ??
  25. ??public?static?void?main(java.lang.String...);??
  26. ????Code:??
  27. ???????0:?new???????????#8??????????????????//?class?LambdaTest??
  28. ???????3:?dup??
  29. ???????4:?invokespecial?#9??????????????????//?Method?"<init>":()V??
  30. ???????7:?astore_1??
  31. ???????8:?aload_1??
  32. ???????9:?getfield??????#4??????????????????//?Field?li1:LlambdaInterface;??
  33. ??????12:?invokeinterface?#10,??1???????????//?InterfaceMethod?lambdaInterface.me:()V??
  34. ??????17:?aload_1??
  35. ??????18:?getfield??????#7??????????????????//?Field?li2:LlambdaInterface;??
  36. ??????21:?invokeinterface?#10,??1???????????//?InterfaceMethod?lambdaInterface.me:()V??
  37. ??????26:?return??
  38. }</span></span></span>??

?

? ?从这里可以看出,lambda代码块和匿名内部类的调用方法是一样的,都是先创建一个方法,然后创建其句柄,这两个句柄分别对应lambdaTest$2.class和lambdaTest$1.class。

? ?其中匿名内部类对应LambdaTest$1,它的字节码为:

?

Txt代码??bubuko.com,布布扣
  1. <span><span><span?style="">class?LambdaTest$1?implements?lambdaInterface?{??
  2. ??final?LambdaTest?this$0;??
  3. ??
  4. ??LambdaTest$1(LambdaTest);??
  5. ????Code:??
  6. ???????0:?aload_0??
  7. ???????1:?aload_1??
  8. ???????2:?putfield??????#1??????????????????//?Field?this$0:LLambdaTest;??
  9. ???????5:?aload_0??
  10. ???????6:?invokespecial?#2??????????????????//?Method?java/lang/Object."<init>":()V??
  11. ???????9:?return??
  12. ??
  13. ??public?void?me();??
  14. ????Code:??
  15. ???????0:?getstatic?????#3??????????????????//?Field?java/lang/System.out:Ljava/io/PrintStream;??
  16. ???????3:?aload_0??
  17. ???????4:?invokevirtual?#4??????????????????//?Method?java/io/PrintStream.println:(Ljava/lang/Object;)V??
  18. ???????7:?return??
  19. }</span></span></span>??

?

? ??lambda代码块对应LambdaTest$2,它的字节码为:

?

Txt代码??bubuko.com,布布扣
  1. <span><span><span?style="">class?LambdaTest$2?implements?lambdaInterface?{??
  2. ??LambdaTest?encl$0;??
  3. ??
  4. ??final?LambdaTest?this$0;??
  5. ??
  6. ??LambdaTest$2(LambdaTest,?LambdaTest);??
  7. ????Code:??
  8. ???????0:?aload_0??
  9. ???????1:?aload_1??
  10. ???????2:?putfield??????#1??????????????????//?Field?this$0:LLambdaTest;??
  11. ???????5:?aload_0??
  12. ???????6:?invokespecial?#2??????????????????//?Method?java/lang/Object."<init>":()V??
  13. ???????9:?aload_0??
  14. ??????10:?aload_2??
  15. ??????11:?putfield??????#3??????????????????//?Field?encl$0:LLambdaTest;??
  16. ??????14:?return??
  17. ??
  18. ??public?void?me();??
  19. ????Code:??
  20. ???????0:?getstatic?????#4??????????????????//?Field?java/lang/System.out:Ljava/io/PrintStream;??
  21. ???????3:?aload_0??
  22. ???????4:?getfield??????#3??????????????????//?Field?encl$0:LLambdaTest;??
  23. ???????7:?invokevirtual?#5??????????????????//?Method?java/io/PrintStream.println:(Ljava/lang/Object;)V??
  24. ??????10:?return??
  25. }??
  26. </span></span></span>??

? ? 从它们的字节码可以很明显的看出,lambda代码块的字节码在初始化时多了putfield指令,在me()运行时,有getfield指令。从解释Field encl$0:LLambdaTest;中可以知道这步骤就是LambdaTest实例引用的入栈和出栈,所以lambda的代码块的this的引用是其调用者LambdaTest.class,这里和匿名内部类不一样。相比较而言,lambda代码块对this的引用更精准。

?

? ? 至此lambda项目的前两个目标已经完成。即简化语法,和明确对象指引。

?

五、lamdab的性能

?

? ??在JDK1.7中,引入了虚拟机新指令,invokedynamic(有点函数指针的味道),将用于支持lambda项目,具体可以参考Rémi Forax 的博客

?

? ??但是目前javap指令还不能直生成带有invokedynamic指令的字节码,不过可以通过别的工具来实现,可以参考周志明的文章。这里引用引用周志明的说明,简单的解释如下(详细的请看他的文章):

?

? ? 每一处含有invokedynamic指令的位置都被称作“动态调用点(Dynamic Call Site)”,这条指令的第一个参数不再是代表方法符号引用的CONSTANT_Methodref_info常量,而是变为JDK 7新加入的CONSTANT_InvokeDynamic_info常量,从这个新常量中可以得到3项信息:引导方法(Bootstrap Method,此方法存放在新增的BootstrapMethods属性中)、方法类型(MethodType)和名称。引导方法是有固定的参数,并且返回值是java.lang.invoke.CallSite对象,这个代表真正要执行的目标方法调用。根据CONSTANT_InvokeDynamic_info常量中提供的信息,虚拟机可以找到并且执行引导方法,从而获得一个CallSite对象,最终调用要执行的目标方法上。

?

? ? ?换句话说就是在虚拟机内部加入了类似C的函数指针功能。从以上的例子中可以看出,目前lambda的代码块是按照匿名内部类的方式进行工作的,即:内部类+方法句柄,那么虚拟机工作的第一步是加载内部类,再调用对应方法,如果使用过于平频繁,那么内部类的new动作开销就比较大了。在invokedynamic指令的帮助下,可以直接调用lambda所对应的方法,而不用创建内部类,这样就避免了“内部类”所带来的加载等问题。目前从lambda的话题库来看是这么解释invokedynamic如何工作和优势的。

? ? 在lamdba项目的说明中已经明确表示,在预览版中将lamdba代码块编译成内部类的形式是暂时的。最终将采用invokedynamic指令来实现,即提升性能的部分最终是invokedynamic指令对函数接口的支持行能力来提升。

?

? ? 不过手痒,还是自己测了下速度,仅供参考:
Java代码??bubuko.com,布布扣
  1. <span><span><span?style="">public?class?LambdaTest?{??
  2. ????lambdaInterface?li1?=?(String?s)->{System.out.println(s);};??
  3. ??????????
  4. ????lambdaInterface?li2?=?new?lambdaInterface(){??
  5. ????????????public?void?me(String?s){??
  6. ????????????????System.out.println(s);??
  7. ????????????}??
  8. ????????};??
  9. ??????
  10. ????public?static?void?main(String...?args)?{??
  11. ????????//这里原来有错误,已经纠正了。??
  12. ????????LambdaTest?lt?=?new?LambdaTest();??
  13. ????????????????long?l1?=?System.currentTimeMillis();??
  14. ????????for?(int?i?=?0;?i?<?1000;?i++)?{??
  15. ????????????lt.li1.me("21");??
  16. ????????}??
  17. ????????long?l2?=?System.currentTimeMillis();??
  18. ????????for?(int?i?=?0;?i?<?1000;?i++)?{??
  19. ????????????lt.li2.me("21");??
  20. ????????}??
  21. ????????long?l3?=?System.currentTimeMillis();??
  22. ????????System.out.println(l2-l1);??
  23. ????????System.out.println(l3-l2);??
  24. ????}??
  25. }</span></span></span>??
? ?运行参数设置-server,其余默认,运行结果为
? ?200
? ?172
?
? ??这么看来似乎lambda语法编译编译的代码块略慢一些。不过考虑的到是预览版,lamdab或者说invokedynamic指令的实际性能估计得等到JDK1.8正式发布才能一窥究竟。
?
? ? 仅管性能方面还没进步,不过灵活的语法已经带来了很多便利,对于脚本语言来说更方便。另外在闭包的意义上来说也更加完美。总的来说Lamdba还是很值得期待的。
?
bubuko.com,布布扣

JAVA8 十大新特性详解

原文:http://fulei686.iteye.com/blog/2183113

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!