最近在学习java8的新特性。 首先先来安装java8。 Java8 在 win7下很容易安装,但是官方不支持win XP. windows Xp java8无法安装。如果在xp下安装会报这样的错误:
无法定位程序输入点 RegDeleteKeyExA 于动态链接库 ADVAPI32.dll
如果非要在32位的xp系统,可以先下载java8的jdk的exe文件。然后使用7zip进行解压。再解压tools.zip文件。最后你能得到下面的目录结构:
在这个目录下执行如下的命令:
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的新特性了。
public interface TestDefault { default void pirnt(){下面的图里没有编译错误
System.out.println();
}
}
对于java8, 网上说了55条新特性。但详细看来,最有用的也就那么几条。下面罗列一下:
1. 消除持久代. 再也没有OutOfMemoryError. PermGen Space 这样的错误了。持久代(Interned 字符串, 类元数据和类静态变量)合并入Heap Space或者direct buffer. 以后可能更多的会OutOfMemoryError: Heap space.
2. Annotation更加强大。可以在函数参数中加入@notnull 这样的标记,来判断参数是否为null. 另外还有可以用来消除public, private 这些关键字。
3. 加密方面,AES使用了CPU指令,可以更快加解密。用SHA-224代替SHA-1. 支持了64位windows的PKCS#11,加解密AES,RSA支持更多的PKCS。
4. 加入JS 引擎。 可以运行js脚本。这对node.js, webview的使用意义更大
5. Http URL, 支持Http GET, POST 操作,这个是要在未来代替HttpClient吗?
6.使用平衡树处理HashMap中的冲突,对于频繁的冲突会将List替换成平衡树。这样做会提高HashMap的插入速度。
7. Base64终于可以名正言顺的使用。java.util.Base64.Encoder 和java.util.Base64.Decoder. 而不要使用过去的sun.misc.BASE64Encoder等。
8. 并发的支持。增加适用于频繁更新但非频繁读取的原子变量。ConcurrentHashMap更新,Fork-Join框架更新。Arrays.sort有了并发的sort => Arrays.parallelSort, 可以很好的利用多核的资源。据说性能在数据均衡的情况下最少提升了30%。
9. lamda表达式引入,接口中的default function以及stream
对于以上几点主要说说第8和第9点。对于第8点做了个小实验验证了一下并发情况下的排序性能提升:
public class TestJava8 { public static void main(String[] args) { int count =10000000; int[] k = new int[count]; Random r = new Random(); for (int j= 0; j < 5; j++) { for (int i = 0;i<count; i++) { k[i] = r.nextInt(); } int [] g = Arrays.copyOf(k, k.length); int [] t = Arrays.copyOf(k, k.length); long start = System.currentTimeMillis(); Arrays.parallelSort(g); System.out.println(System.currentTimeMillis() - start+":parallel:"); start = System.currentTimeMillis(); Arrays.sort(t); System.out.println(System.currentTimeMillis() - start+":serial:"); } }}
在公司里的伪双核的机器上跑。反复几次,当如也有可能期间夹杂了gc()的影响。结果基本是这样的:
1398:parallel:1373:serial:1224:parallel:1287:serial:1261:parallel:1274:serial:1304:parallel:1314:serial:1234:parallel:1480:serial:
从结果上看,基本上并发还是需要使用在数据量较大的情况下。但是如果数据量大是否能使用内存排序还是个问题。对于parallelSort, Java8还是使用的Fork-Join的模式。对于小数据量效果并不佳,因为开启线程还是要费些时间的。我个人认为java8 对于Arrays.sort()这个接口,还是默默的做并发/非并发判断比较好。毕竟多核都对java程序员隐藏的话,并发sort()啥的最好也能对java程序员隐藏。否则又要在上面包一层先判断一下数据量大小,然后再执行sort() or parallelSort(). 另外官方所说的数据均衡的情况下最少提升了30%。 这个意思是说官方的测试在比较均衡的数据集跑出来的,但是一般在生产环境中的数据集是不均衡的。
java8 最大的亮点就是lamda表达式。可以使用java进行函数式编程。这样的好处是什么呢?
1. 如果lamda表达式只对变量进行只读操作,那一定是threadSafe. 非只读的情况需要特殊判读是否是threadSafe.
2. 如果是threadSafe 一定可以并发执行
3. 从参数推出结果,是针对API接口编程。(这点有些虚)
4. 返回的结果也可以是函数
一段代码这样写:
// 求一个List中的最大值public static void main(String[] args) {List<Integer> k = new ArrayList<Integer>();k.add(1);k.add(2);k.add(3);k.add(8);k.add(11);k.add(20);k.add(50);int max = Integer.MIN_VALUE;for (int j : k) {if (max < j) {max = j;}}System.out.println(max);}
假如这段代码中查找最大值成了性能瓶颈,那么需要将这段代码改为并发操作。想想就不容易。那现在使用lamda表达式这样写:
// 求一个链表中的最大值public static void main(String[] args) {List<Integer> k = new ArrayList<Integer>();k.add(1);k.add(2);k.add(3);k.add(8);k.add(11);k.add(20);k.add(50);Optional<Integer> max = k.stream().max((a,b) -> {return a-b;});System.out.println(max);}
想变成并发操作这样写:
// 求一个链表中的最大值public static void main(String[] args) {List<Integer> k = new ArrayList<Integer>();k.add(1);k.add(2);k.add(3);k.add(8);k.add(11);k.add(20);k.add(50);Optional<Integer> max = k.parallelStream().max((a,b) -> {return a-b;});System.out.println(max);}
很简单吧。就是将k.stream() 改成 k.parallelStream()
而这个之所以简单是基于这样几点。
1. 编写ThreadSafe的函数或者lamda表达式, 本身就很容易变为多线程操作。
2. Stream方式处理数据。
3. 传入的参数数据只关系上一步的结果,而不关系其他数据在本步所做操作产生的中间数据。
4. Fork-Join提供了很好的多线程框架
这里八卦一下Java8 为什么做了除了lamda表达式之外的一些事情。
1. interface中的default function接口。 原因是lamda表达式是为了并发做的。那么并发包concurrent中已经有了很多接口,希望和过去的版本兼容,减少迁移的工作量。如果interface中新增了接口,那么使用jdk8以前的代码就会有编译错误。要解决这个编译错误,要么抽象出一个abstract class实现接口,并加入缺省的实现。要么在以前的所有的实现中都加入这个接口。所以听听就觉得这次迁移是个大的工作量。那default function就诞生了。为了不写abstract class, 并且使用jdk5,jdk6,jdk7的代码可以方便迁移到jdk8中。default function 这样使用:
public class TestF {public interface TestDefault {default void print(){System.out.println("ew2we");}}public static void main(String[] args) {// 写个匿名类new TestDefault(){}.print();}}
另外值得注意的一点就是:如果有两个interface实现了同名的default function. 那编译会报错。
2. SAM (single abstract method) 也就是这个Annotation @FunctionalInterface. 如果带上这个Annotation,那么这个接口仅且只有一个未实现的方法。当然可以有多个已经实现的default 方法。这个是为了lamda表达式做的。lamda表达式是形如: (param list)-> {code statements;} ,基本上可以看作是一个函数。所以要求仅且只有一个函数方法没有实现就是为了代码好看。@FunctionalInterface 写法如下:
@FunctionalInterfacepublic interface DefaultInterface { default void hello() { System.out.println("hello"); } default void zzz() { System.out.println("zzz"); } void z(); // 只有一个未实现的函数}
3. Stream 是为了更好的使用Fork-Join框架。Stream就是pipeline. 对于只关心输入和输出的API来说。编写Stream方式的代码非常容易实现并发。其实就是把一组相似的数据丢到一个池中,然后线程池针对这些数据开始做事情。在执行完毕之前,需要join所有的线程,将最终结果放入到最终返回的池中并返回。而流式的操作最简单的就是调用方便。可以理解为以前所说的链式调用。但是对于这种编写代码的Style,还是尽量将每次调用写在不同的行中,这样会方便调试。下面是Stream的例子:
public static void main(String[] args) {List<Integer> k = new ArrayList<Integer>();k.add(1);k.add(2);k.add(3);k.add(8);k.add(11);k.add(20);k.add(50); // 找到list中大于2的三个数,不要求顺序,最后打印出来k.parallelStream().filter((a)->{return a>2;}).unordered().limit(3).forEach((a) -> {System.out.println(a);}); // 获取大于1的list元素中的最大值。如果list中的元素都不大于1,那么返回Optional.emptyOptional<Integer> max = k.parallelStream().filter((a)->{return a>1;}).max((a,b) -> {return a-b;});System.out.println(max);}
这里简单介绍一下lamda表达式中语法。 这个lamda表达式就是实现了一个抽象方法。所以需要注意那个抽象方法到底是不是void返回,这关系到你的表达式中是否有return.lamda的语法是前面是参数列表,0个参数或者多于一个参数时必须使用圆括号括起。如果只有一个参数,那么可以省略圆括号。然后是一个箭头->。 再后面是函数体。函数体需要使用花括号括起,如果只有一条语句,可以省略花括号。下面的lamda表达式都是对的。
a -> return a+1;(a) -> return a+1;(a) -> {return a+1;}(a,b) -> {int c = a+b; return c>>1;}()->System.out.println("empty");
另外注意一下final 变量和this指针。
import java.util.ArrayList;import java.util.Arrays;import java.util.List;import java.util.Optional;public class TestLamba {public static class Score {int score;public List<Integer> findLarger(List<Integer> scores) {List<Integer> tmp = new ArrayList<Integer>();//注意这里的this是 Score Object的thisscores.stream().filter((a) -> {return a > this.score;}).forEach((a)->{tmp.add(a);});return tmp;}public static List<Integer> findLarger(List<Integer> scores, Score myScore) {List<Integer> tmp = new ArrayList<Integer>();//myScore = new Score(); compile wrong//这里的myScore在语义上是final的, 但是myScore中的域可以变化//myScore.score = 3; 这个是正确的,放在lamda表达式中也正确。//所以对于myScore而言lamda表达式并不是threadSafescores.stream().filter((a) -> {return a > myScore.score;}).forEach((a)->{tmp.add(a);});return tmp;}}public static void main(String[] args) {Arrays.asList().forEach((a)->System.out.println("I'm "+a));List<Integer> k = new ArrayList<Integer>();k.add(1);k.add(2);k.add(3);k.add(8);k.add(11);k.add(20);k.add(50);Score s = new Score();s.score = 11;s.findLarger(k).forEach(a->{System.out.println(a);});System.out.println("dsdsd");Score.findLarger(k, s).forEach(a->{System.out.println(a);});}}
参数里面没有final变量,java8在语法上并不要求外部引用参数是final,但是语义上还是final的,这个类似匿名内部类。另外this指针一定是外层类的指针。lamda表达式是没有this的。
现在java的jdk已经可能非常容易的使用多核资源。所以非常期待java以后的jdk能够利用多机器的资源例如现在的map/reduce.这样写程序就越来越简单了。看起来就是这样一个趋势~~~继续进化吧~