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提供了三個標准來確定用哪一個。

  1. 類或父類的方法優先級高於接口默認的方法。
  2. 如果上面不行的話,誰擁有最具體的實現的話,就用誰。
  3. 如果都不能確定的情況下,就必須顯性的調用。來指定他要調哪一個。

舉例子。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();
    }
}

 當然除了上面之外,你還是可以定義靜態方法和常量。這個時候有人就會說他不是跟抽象類很像嗎?是很像。可是不一樣子。抽象類是不是可以實例一個字段。但是接口卻不行。還有抽像類你只能單繼承。接口就可以多繼承了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM