JAVA8给我带了什么——并行流和接口新功能
流,确定是笔者内心很向往的天堂,有他之后JAVA在处理数据就变更加的灵动。加上lambda表达不喜欢都不行。JAVA8也为流在提供另一个功能——并行流。即是有并行流,那么是不是也有顺序流。没有错。我前面操作的一般都是顺序流。在JAVA8里面并行流和顺序流是可以转变的。来看一个例子——笔者打印数字。
1 package com.aomi; 2 3 import java.util.stream.LongStream; 4 5 public class Main { 6 7 public static void main(String[] args) { 8 // TODO Auto-generated method stub 9 10 LongStream.range(0, 10).forEach(i -> { 11 System.out.print(i + " "); 12 }); 13 } 14 15 }
LongStream.range这个方法是来获取数字的。这里表示获得0到10,但不含10 的数字。运行结果:
现在让我们把他换成并行来看看。
1 package com.aomi; 2 3 import java.util.stream.LongStream; 4 5 public class Main { 6 7 public static void main(String[] args) { 8 // TODO Auto-generated method stub 9 10 LongStream.range(0, 10).parallel().forEach(i -> { 11 System.out.print(i + " "); 12 }); 13 } 14 15 }
运行结果:
俩个结果相比一下,我们就可以明显他们发生了变化。我们只是加一个parallel函数就发生好多的变化。笔者本来是要讲他们之间的性能比较的。不敢,因为笔者试好还有个例子。却发现有时候顺序流都比并行流来快。上面是顺序流转并行流。在来看一下相反的。
1 public static void main(String[] args) { 2 // TODO Auto-generated method stub 3 4 List<Integer> datas = Arrays.asList(1,2,3,4,56); 5 6 datas.parallelStream().forEach(i -> { 7 System.out.print(i + " "); 8 }); 9 }
parallelStream函数就是用来建一个并行流的。运行结果:
转为顺序流
1 public static void main(String[] args) { 2 // TODO Auto-generated method stub 3 4 List<Integer> datas = Arrays.asList(1,2,3,4,56); 5 6 datas.parallelStream().sequential().forEach(i -> { 7 System.out.print(i + " "); 8 }); 9 }
运行结果:
我们都知道流里面用到了JAVA7里面的分支和合并的框架来进行的。古代有一个词叫分而治之。把一个事情分为几个小事件。然面各自处理。所以了解代码里面是什么样子折分成小事件是非常重要的。他有俩个关键字Fork和Join。Fork方法你可以理解为拆分,并压入线程队列中。而Join就是合并的意思了。来笔者来写一个试。
1 package com.aomi; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 import java.util.concurrent.RecursiveTask; 6 7 public class DistinctCharForkJoin extends RecursiveTask<List<Character>> { 8 9 private List<Character> chars; 10 11 public DistinctCharForkJoin(List<Character> chars) { 12 this(chars, 0, chars.size()); 13 } 14 15 public DistinctCharForkJoin(List<Character> chars, int start, int end) { 16 17 this.chars = chars.subList(start, end); 18 } 19 20 @Override 21 protected List<Character> compute() { 22 // TODO Auto-generated method stub 23 List<Character> tmpChars = new ArrayList<Character>(); 24 25 // 判断不可以在拆分了 26 if (this.chars.size() < 3) { 27 28 for (Character character : chars) { 29 if (!tmpChars.contains(character)) 30 tmpChars.add(character); 31 } 32 33 } else {// 表示可以在拆分。 34 35 int len = this.chars.size(); 36 37 // 建立左边的小事件 38 DistinctCharForkJoin leftForkJoin = new DistinctCharForkJoin(chars, 0, len / 2); 39 40 leftForkJoin.fork(); 41 42 // 建立右边的小事件 43 DistinctCharForkJoin rightForkJoin = new DistinctCharForkJoin(chars, len / 2, len); 44 45 rightForkJoin.fork(); 46 47 List<Character> rChars = rightForkJoin.join(); 48 49 List<Character> lChars = leftForkJoin.join(); 50 51 // 俩个合并。 52 for (Character character : rChars) { 53 if (!tmpChars.contains(character)) 54 tmpChars.add(character); 55 } 56 57 for (Character character : lChars) { 58 if (!tmpChars.contains(character)) 59 tmpChars.add(character); 60 } 61 62 } 63 64 return tmpChars; 65 } 66 67 }
Main:
1 public static void main(String[] args) { 2 // TODO Auto-generated method stub 3 4 List<Character> chars = Arrays.asList('a', 'b', 'c', 'd', 'b', 'a'); 5 6 DistinctCharForkJoin task = new DistinctCharForkJoin("main", chars); 7 8 List<Character> resChars = new ForkJoinPool().invoke(task); 9 10 for (Character character : resChars) { 11 12 System.out.print(character +" "); 13 } 14 }
运行结果:
你们一定很奇怪为什么笔者会讲到JAVA7带来的东西呢?JAVA8引入了一个新的接口——Spliterator接口。人称可分迭代器。如果你有心去看一个接口List的话,你可能会发现一个方法。如下
1 default Spliterator<E> spliterator() { 2 return Spliterators.spliterator(this, Spliterator.ORDERED); 3 }
Spliterator接口:
1 public interface Spliterator<T> { 2 boolean tryAdvance(Consumer<? super T> action); 3 Spliterator<T> trySplit(); 4 long estimateSize(); 5 int characteristics(); 6 }
讲JAVA7里面的分支/合并的目地就是为了理解Spliterator接口的作用。如下
- tryAdvance:用于遍历当前的元素。如果还有的话,就返回true;
- trySplit:用于拆分。如果当前不可以在拆分的话,就返回null;跟上面的compute方法很像。
- estimateSize:表示还需要遍历的元素有多少。
- characteristics:表示当前处理的数据是什么样子的。比如是否有序,每一元素是否为null。上面Spliterator接口的代码是笔者去掉大部分复制出来。这个值都在代码中。作用你们可以自己去看一下代码就是知道。
要注意Spliterator接口只是用去拆分任务的作用。JAVA8帮你做了很多拆分的功能。大部分你可以不用自己写。当然如果你想要自己动手。你只要实现这样子就可以了。如下
1 package com.aomi; 2 3 import java.util.List; 4 import java.util.Spliterator; 5 import java.util.function.Consumer; 6 7 public class DistinctCharSpliterator implements Spliterator<Character> { 8 9 private List<Character> chars; 10 private int index = 0; 11 12 public DistinctCharSpliterator(List<Character> chars) { 13 this.chars = chars; 14 } 15 16 public DistinctCharSpliterator(List<Character> chars, int start, int end) { 17 this.chars = chars.subList(start, end); 18 } 19 20 @Override 21 public boolean tryAdvance(Consumer<? super Character> action) { 22 // TODO Auto-generated method stub 23 action.accept(this.chars.get(index++)); 24 return index < this.chars.size(); 25 } 26 27 @Override 28 public Spliterator<Character> trySplit() { 29 // TODO Auto-generated method stub 30 int difLen = this.chars.size() - index; 31 32 // 判断不可以在拆分了 33 if (difLen < 3) { 34 return null; 35 } else {// 表示可以在拆分。 36 37 38 DistinctCharSpliterator spliterator = new DistinctCharSpliterator(chars.subList(index, index + 2)); 39 40 index = index + 2; 41 42 return spliterator; 43 44 } 45 } 46 47 @Override 48 public long estimateSize() { 49 // TODO Auto-generated method stub 50 return this.chars.size() - index; 51 } 52 53 @Override 54 public int characteristics() { 55 // TODO Auto-generated method stub 56 // 有序 元素不空 遍历过程不能删除,和修改 增加 57 return ORDERED + NONNULL + IMMUTABLE; 58 } 59 60 }
Main:
1 public static void main(String[] args) { 2 // TODO Auto-generated method stub 3 4 List<Character> chars = Arrays.asList('a', 'b', 'c', 'd', 'b', 'a'); 5 6 DistinctCharSpliterator distinctCharSpliterator = new DistinctCharSpliterator(chars); 7 8 Stream<Character> stream = StreamSupport.stream(distinctCharSpliterator, true); 9 10 stream.distinct().forEach((Character ch) -> { 11 12 System.out.print(ch+" "); 13 }); 14 15 }
运行结果:
上面的例子有一点烂。但是大家可以复制做一下继点去看看他的执行过程。就可以看出很多东西来。主要是理解这个原理就可以了。
流的并行功能并没有让笔者有多心动。真正让笔者感觉不错的要属于JAVA8对接口的升级。什么意思?笔者不清楚有多少个人写个框架或是读过框架源码,一般框架里面都会用到一些面向接口的编程模式。那个或多或少会有这样子感觉。一但项目发布出去,这个时候你想要修改接口。比如在接口里面增加一个新的功能方法。这样子时候你就不得不考虑一下外面有多少个人在实现你现在框架的接口。因为你增加一个接口的新方法。别人也要跟着实现,不然的一定会报错或是运行时候报错。不管哪一种都是设计者不想看到的。
JAVA8现在可以让你定义接口的默认方法。什么思意呢?让笔得写一个例子。
Base接口:
1 package com.aomi; 2 3 public interface Base { 4 void call(); 5 }
BaseA类:
1 package com.aomi; 2 3 public class BaseA implements Base { 4 5 @Override 6 public void call() { 7 8 } 9 10 }
Main:
1 package com.aomi; 2 3 public class Main { 4 5 public static void main(String[] args) { 6 // TODO Auto-generated method stub 7 8 Base baseA = new BaseA(); 9 10 baseA.call(); 11 } 12 }
上面的代码没有什么特别的。现在笔者在加一个方法。看一个他会不会有问题。如下
base类:
1 package com.aomi; 2 3 public interface Base { 4 void call(); 5 void call2(); 6 }
结果:
看到吧。BaseA类马上就报错。现在笔者在加上一个默认的方法会什么呢?
package com.aomi; public interface Base { void call(); default void call2() { System.out.println("default call2"); } }
Main修改一下吧。
1 package com.aomi; 2 3 public class Main { 4 5 public static void main(String[] args) { 6 // TODO Auto-generated method stub 7 8 Base baseA = new BaseA(); 9 10 baseA.call2(); 11 } 12 }
运行结果:
上面的代码。笔者在BaseA类里面并没有实现call2的方法。显然现在的功能对我们写框架的人来写太棒了。在也不用担心增加一个接方法而去考虑有多少个人用这个接口了。
那么问题来了。我们在写代码的过程中,一定会遇到方法相同的情况吧。这个时候JAVA8提供了三个标准来确定用哪一个。
- 类或父类的方法优先级高于接口默认的方法。
- 如果上面不行的话,谁拥有最具体的实现的话,就用谁。
- 如果都不能确定的情况下,就必须显性的调用。来指定他要调哪一个。
举例子。A和B都是接口。其中B继承了A。同时C实现了A和B。这个时候调用C会是什么样子。
A:
public interface A { default void call() { System.out.println("A call"); } }
B:
public interface B extends A { default void call() { System.out.println("B call"); } }
C:
public class C implements A, B { }
D:
public static void main(String[] args) { // TODO Auto-generated method stub C c = new C(); c.call(); }
运行结果:
上面A和B都是接口。他们有call方法。其中关键是B继承了。说明B拥有A的一切方法。那么是不是说B就是最具体实现的。如果你们只用第一个标准的话,那是肯定不行的。
还是简单一点,我们把B继承A的这个关系去掉,在来看看。
不好意思好像报错了。所以只能苦一下了。显性调用。
package com.aomi; public class C implements B, A { public void call() { B.super.call(); } }
当然除了上面之外,你还是可以定义静态方法和常量。这个时候有人就会说他不是跟抽象类很像吗?是很像。可是不一样子。抽象类是不是可以实例一个字段。但是接口却不行。还有抽像类你只能单继承。接口就可以多继承了。