鲜活的Java 8 --- Java 8 进化之路
最近在学习java8的新特性。 首先先来安装java8。 Java8 在 win7下很容易安装,但是官方不支持win XP. windows Xp java8无法安装。如果在xp下安装会报这样的错误:
无法定位程序输入点 RegDeleteKeyExA 于动态链接库 ADVAPI32.dll
如果非要在32位的xp系统,可以先下载java8的jdk的exe文件。然后使用7zip进行解压。再解压tools.zip文件。最后你能得到下面的目录结构:
在这个目录下执行如下的命令:
1 | FOR /R %f IN (*.pack) DO "d:\java8\bin\unpack200.exe" -r -v "%f" "%~pf%~nf.jar" |
注意d:\java8\bin\unpack200.exe 这个。这个是你的解压路径中的unpack200.exe。上面的命令就是使用解压中的unpack200.exe将整个解压路径中的.pack文件转换为.jar文件. 转换完之后,d:\java8这个路径就已经是JAVA_HOME路径。
下面就是IDE也能使用java1.8的新特性。下载一个新版本的eclipse。另外如果是eclipse kepler 可以在eclipse market中下载一个java8的插件"Eclipse Java Development Tools Patch with Java 8 support(for Kepler SR2)"。 然后在tab中windows->Pererfence->java->Installed JREs => “add…”=> Standard VM => JRE home选择之前的那个解压路径。剩下的内容eclipse会帮你填充。然后一路点击确认就可以了。
然后将execute Environments 选中 javaSE-1.8, 然后选中 Java8 ,点击确定。
新建立一个工程。然后将Java Compiler选中 1.8的版本。
然后新建一个interface。编写如下代码。如果IDE没有报错。那么eclipse IDE就已经可以使用java8的新特性了。
1 |
|
下面的图里没有编译错误
对于java8, 网上说了55条新特性。但详细看来,最有用的也就那么几条。下面罗列一下:
- 消除持久代. 再也没有OutOfMemoryError. PermGen Space 这样的错误了。持久代(Interned 字符串,类元数据和类静态变量)合并入Heap Space或者direct buffer. 以后可能更多的会OutOfMemoryError: Heap space.
- Annotation更加强大。可以在函数参数中加入@notnull 这样的标记,来判断参数是否为null. 另外还有可以用来消除public,private 这些关键字。
- 加密方面,AES使用了CPU指令,可以更快加解密。用SHA-224代替SHA-1.支持了64位windows的PKCS#11,加解密AES,RSA支持更多的PKCS。
- 加入JS 引擎。 可以运行js脚本。这对node.js, webview的使用意义更大
- Http URL, 支持Http GET, POST 操作,这个是要在未来代替HttpClient吗?
- 使用平衡树处理HashMap中的冲突,对于频繁的冲突会将List替换成平衡树。这样做会提高HashMap的插入速度。
- Base64终于可以名正言顺的使用。java.util.Base64.Encoder 和java.util.Base64.Decoder.而不要使用过去的sun.misc.BASE64Encoder等。
- 并发的支持。增加适用于频繁更新但非频繁读取的原子变量。ConcurrentHashMap更新,Fork-Join框架更新。Arrays.sort有了并发的sort => Arrays.parallelSort,可以很好的利用多核的资源。据说性能在数据均衡的情况下最少提升了30%。
- lamda表达式引入,接口中的default function以及stream
对于以上几点主要说说第8和第9点。对于第8点做了个小实验验证了一下并发情况下的排序性能提升:
1 |
|
在公司里的伪双核的机器上跑。反复几次,当如也有可能期间夹杂了gc()的影响。结果基本是这样的:
1 |
|
从结果上看,基本上并发还是需要使用在数据量较大的情况下。但是如果数据量大是否能使用内存排序还是个问题。对于parallelSort,Java8还是使用的Fork-Join的模式。对于小数据量效果并不佳,因为开启线程还是要费些时间的。我个人认为java8 对于Arrays.sort()这个接口,还是默默的做并发/非并发判断比较好。毕竟多核都对java程序员隐藏的话,并发sort()啥的最好也能对java程序员隐藏。否则又要在上面包一层先判断一下数据量大小,然后再执行sort() or parallelSort(). 另外官方所说的数据均衡的情况下最少提升了30%。这个意思是说官方的测试在比较均衡的数据集跑出来的,但是一般在生产环境中的数据集是不均衡的。
java8 最大的亮点就是lamda表达式。可以使用java进行函数式编程。这样的好处是什么呢?
- 如果lamda表达式只对变量进行只读操作,那一定是threadSafe. 非只读的情况需要特殊判读是否是threadSafe.
- 如果是threadSafe 一定可以并发执行
- 从参数推出结果,是针对API接口编程。(这点有些虚)
- 返回的结果也可以是函数
一段代码这样写:
1 |
|
假如这段代码中查找最大值成了性能瓶颈,那么需要将这段代码改为并发操作。想想就不容易。那现在使用lamda表达式这样写:
1 |
|
想变成并发操作这样写:
1 |
|
很简单吧。就是将k.stream() 改成 k.parallelStream() 而这个之所以简单是基于这样几点。
-
编写ThreadSafe的函数或者lamda表达式, 本身就很容易变为多线程操作。
-
Stream方式处理数据。
-
传入的参数数据只关系上一步的结果,而不关系其他数据在本步所做操作产生的中间数据。
-
Fork-Join提供了很好的多线程框架
这里八卦一下Java8 为什么做了除了lamda表达式之外的一些事情。
- interface中的default function接口。 原因是lamda表达式是为了并发做的。那么并发包concurrent中已经有了很多接口,希望和过去的版本兼容,减少迁移的工作量。如果interface中新增了接口,那么使用jdk8以前的代码就会有编译错误。要解决这个编译错误,要么抽象出一个abstract class实现接口,并加入缺省的实现。要么在以前的所有的实现中都加入这个接口。所以听听就觉得这次迁移是个大的工作量。那default function就诞生了。为了不写abstract class, 并且使用jdk5,jdk6,jdk7的代码可以方便迁移到jdk8中。default function 这样使用:
1 |
|
另外值得注意的一点就是:如果有两个interface实现了同名的default function. 那编译会报错。
- SAM (single abstract method) 也就是这个Annotation @FunctionalInterface.如果带上这个Annotation,那么这个接口仅且只有一个未实现的方法。当然可以有多个已经实现的default方法。这个是为了lamda表达式做的。lamda表达式是形如: (param list)-> {code statements;},基本上可以看作是一个函数。所以要求仅且只有一个函数方法没有实现就是为了代码好看。@FunctionalInterface 写法如下:
1 |
|
- Stream 是为了更好的使用Fork-Join框架。Stream就是pipeline. 对于只关心输入和输出的API来说。编写Stream方式的代码非常容易实现并发。其实就是把一组相似的数据丢到一个池中,然后线程池针对这些数据开始做事情。在执行完毕之前,需要join所有的线程,将最终结果放入到最终返回的池中并返回。而流式的操作最简单的就是调用方便。可以理解为以前所说的链式调用。但是对于这种编写代码的Style,还是尽量将每次调用写在不同的行中,这样会方便调试。下面是Stream的例子:
1 |
|
这里简单介绍一下lamda表达式中语法。 这个lamda表达式就是实现了一个抽象方法。所以需要注意那个抽象方法到底是不是void返回,这关系到你的表达式中是否有return.lamda的语法是前面是参数列表,0个参数或者多于一个参数时必须使用圆括号括起。如果只有一个参数,那么可以省略圆括号。然后是一个箭头->。再后面是函数体。函数体需要使用花括号括起,如果只有一条语句,可以省略花括号。下面的lamda表达式都是对的。
1 |
|
另外注意一下final 变量和this指针。
1 |
|
参数里面没有final变量,java8在语法上并不要求外部引用参数是final,但是语义上还是final的,这个类似匿名内部类。另外this指针一定是外层类的指针。lamda表达式是没有this的。现在java的jdk已经可能非常容易的使用多核资源。所以非常期待java以后的jdk能够利用多机器的资源例如现在的map/reduce.这样写程序就越来越简单了。看起来就是这样一个趋势~~~继续进化吧~