JAVA8給我帶了什麼——並流行和介面新功能
流,確定是筆者內心很嚮往的天堂,有他之後JAVA在處理資料就變更加的靈動。加上lambda表達不喜歡都不行。JAVA8也為流在提供另一個功能——並行流。即是有並行流,那麼是不是也有順序流。沒有錯。我前面操作的一般都是順序流。在JAVA8裡面並行流和順序流是可以轉變的。來看一個例子——筆者列印數字。
1 package com.aomi; 2 3 import java.util.stream.LongStream; 4 5 public class Main { 6 7public static void main(String[] args) { 8// TODO Auto-generated method stub 9 10LongStream.range(0, 10).forEach(i -> { 11System.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 7public static void main(String[] args) { 8// TODO Auto-generated method stub 9 10LongStream.range(0, 10).parallel().forEach(i -> { 11System.out.print(i + " "); 12}); 13} 14 15 }
執行結果:
倆個結果相比一下,我們就可以明顯他們發生了變化。我們只是加一個parallel函式就發生好多的變化。筆者本來是要講他們之間的效能比較的。不敢,因為筆者試好還有個例子。卻發現有時候順序流都比並行流來快。上面是順序流轉並行流。在來看一下相反的。
1 public static void main(String[] args) { 2// TODO Auto-generated method stub 3 4List<Integer> datas = Arrays.asList(1,2,3,4,56); 5 6datas.parallelStream().forEach(i -> { 7System.out.print(i + " "); 8}); 9}
parallelStream函式就是用來建一個並行流的。執行結果:
轉為順序流
1 public static void main(String[] args) { 2// TODO Auto-generated method stub 3 4List<Integer> datas = Arrays.asList(1,2,3,4,56); 5 6datas.parallelStream().sequential().forEach(i -> { 7System.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 9private List<Character> chars; 10 11public DistinctCharForkJoin(List<Character> chars) { 12this(chars, 0, chars.size()); 13} 14 15public DistinctCharForkJoin(List<Character> chars, int start, int end) { 16 17this.chars = chars.subList(start, end); 18} 19 20@Override 21protected List<Character> compute() { 22// TODO Auto-generated method stub 23List<Character> tmpChars = new ArrayList<Character>(); 24 25// 判斷不可以在拆分了 26if (this.chars.size() < 3) { 27 28for (Character character : chars) { 29if (!tmpChars.contains(character)) 30tmpChars.add(character); 31} 32 33} else {// 表示可以在拆分。 34 35int len = this.chars.size(); 36 37// 建立左邊的小事件 38DistinctCharForkJoin leftForkJoin = new DistinctCharForkJoin(chars, 0, len / 2); 39 40leftForkJoin.fork(); 41 42// 建立右邊的小事件 43DistinctCharForkJoin rightForkJoin = new DistinctCharForkJoin(chars, len / 2, len); 44 45rightForkJoin.fork(); 46 47List<Character> rChars = rightForkJoin.join(); 48 49List<Character> lChars = leftForkJoin.join(); 50 51// 倆個合併。 52for (Character character : rChars) { 53if (!tmpChars.contains(character)) 54tmpChars.add(character); 55} 56 57for (Character character : lChars) { 58if (!tmpChars.contains(character)) 59tmpChars.add(character); 60} 61 62} 63 64return tmpChars; 65} 66 67 }
Main:
1 public static void main(String[] args) { 2// TODO Auto-generated method stub 3 4List<Character> chars = Arrays.asList('a', 'b', 'c', 'd', 'b', 'a'); 5 6DistinctCharForkJoin task = new DistinctCharForkJoin("main", chars); 7 8List<Character> resChars = new ForkJoinPool().invoke(task); 9 10for (Character character : resChars) { 11 12System.out.print(character +" "); 13} 14}
執行結果:
你們一定很奇怪為什麼筆者會講到JAVA7帶來的東西呢?JAVA8引入了一個新的介面——Spliterator介面。人稱可分迭代器。如果你有心去看一個介面List的話,你可能會發現一個方法。如下
1 default Spliterator<E> spliterator() { 2return Spliterators.spliterator(this, Spliterator.ORDERED); 3}
Spliterator介面:
1 public interface Spliterator<T> { 2boolean tryAdvance(Consumer<? super T> action); 3Spliterator<T> trySplit(); 4long estimateSize(); 5int 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 9private List<Character> chars; 10private int index = 0; 11 12public DistinctCharSpliterator(List<Character> chars) { 13this.chars = chars; 14} 15 16public DistinctCharSpliterator(List<Character> chars, int start, int end) { 17this.chars = chars.subList(start, end); 18} 19 20@Override 21public boolean tryAdvance(Consumer<? super Character> action) { 22// TODO Auto-generated method stub 23action.accept(this.chars.get(index++)); 24return index < this.chars.size(); 25} 26 27@Override 28public Spliterator<Character> trySplit() { 29// TODO Auto-generated method stub 30int difLen = this.chars.size() - index; 31 32// 判斷不可以在拆分了 33if (difLen < 3) { 34return null; 35} else {// 表示可以在拆分。 36 37 38DistinctCharSpliterator spliterator = new DistinctCharSpliterator(chars.subList(index, index + 2)); 39 40index = index + 2; 41 42return spliterator; 43 44} 45} 46 47@Override 48public long estimateSize() { 49// TODO Auto-generated method stub 50return this.chars.size() - index; 51} 52 53@Override 54public int characteristics() { 55// TODO Auto-generated method stub 56// 有序 元素不空 遍歷過程不能刪除,和修改 增加 57return ORDERED + NONNULL + IMMUTABLE; 58} 59 60 }
Main:
1 public static void main(String[] args) { 2// TODO Auto-generated method stub 3 4List<Character> chars = Arrays.asList('a', 'b', 'c', 'd', 'b', 'a'); 5 6DistinctCharSpliterator distinctCharSpliterator = new DistinctCharSpliterator(chars); 7 8Stream<Character> stream = StreamSupport.stream(distinctCharSpliterator, true); 9 10stream.distinct().forEach((Character ch) -> { 11 12System.out.print(ch+" "); 13}); 14 15}
執行結果:
上面的例子有一點爛。但是大家可以複製做一下繼點去看看他的執行過程。就可以看出很多東西來。主要是理解這個原理就可以了。
流的並行功能並沒有讓筆者有多心動。真正讓筆者感覺不錯的要屬於JAVA8對介面的升級。什麼意思?筆者不清楚有多少個人寫個框架或是讀過框架原始碼,一般框架裡面都會用到一些面向介面的程式設計模式。那個或多或少會有這樣子感覺。一但專案釋出出去,這個時候你想要修改介面。比如在接口裡面增加一個新的功能方法。這樣子時候你就不得不考慮一下外面有多少個人在實現你現在框架的介面。因為你增加一個介面的新方法。別人也要跟著實現,不然的一定會報錯或是執行時候報錯。不管哪一種都是設計者不想看到的。
JAVA8現在可以讓你定義介面的預設方法。什麼思意呢?讓筆得寫一個例子。
Base介面:
1 package com.aomi; 2 3 public interface Base { 4void call(); 5 }
BaseA類:
1 package com.aomi; 2 3 public class BaseA implements Base { 4 5@Override 6public void call() { 7 8} 9 10 }
Main:
1 package com.aomi; 2 3 public class Main { 4 5public static void main(String[] args) { 6// TODO Auto-generated method stub 7 8Base baseA = new BaseA(); 9 10baseA.call(); 11} 12 }
上面的程式碼沒有什麼特別的。現在筆者在加一個方法。看一個他會不會有問題。如下
base類:
1 package com.aomi; 2 3 public interface Base { 4void call(); 5void 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 5public static void main(String[] args) { 6// TODO Auto-generated method stub 7 8Base baseA = new BaseA(); 9 10baseA.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 Bextends 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(); } }
當然除了上面之外,你還是可以定義靜態方法和常量。這個時候有人就會說他不是跟抽象類很像嗎?是很像。可是不一樣子。抽象類是不是可以例項一個欄位。但是介面卻不行。還有抽像類你只能單繼承。介面就可以多繼承了。