1. 程式人生 > >010-jdk1.8版本新特性二-Optional類,Stream流

010-jdk1.8版本新特性二-Optional類,Stream流

字段 combine 特征 love 1.8 filter etc 靜態 語句

1.5、Optional類

1、定義

Optional 類是一個可以為null的容器對象。如果值存在則isPresent()方法會返回true,調用get()方法會返回該對象。

Optional 是個容器:它可以保存類型T的值,或者僅僅保存null。Optional提供很多有用的方法,這樣我們就不用顯式進行空值檢測。

Optional 類的引入很好的解決空指針異常。

2、聲明

以下是一個 java.util.Optional<T> 類的聲明:

public final class Optional<T> extends Object

3、類方法

技術分享圖片
序號    方法 & 描述
1    static <T> Optional<T> empty()
返回空的 Optional 實例。

2    boolean equals(Object obj)
判斷其他對象是否等於 Optional。

3    Optional<T> filter(Predicate<? super <T> predicate)
如果值存在,並且這個值匹配給定的 predicate,返回一個Optional用以描述這個值,否則返回一個空的Optional。

4    <U> Optional<U> flatMap(Function<? super
T,Optional<U>> mapper) 如果值存在,返回基於Optional包含的映射方法的值,否則返回一個空的Optional 5 T get() 如果在這個Optional中包含這個值,返回值,否則拋出異常:NoSuchElementException 6 int hashCode() 返回存在值的哈希碼,如果值不存在 返回 07 void ifPresent(Consumer<? super T> consumer) 如果值存在則使用該值調用 consumer , 否則不做任何事情。 8 boolean
isPresent() 如果值存在則方法會返回true,否則返回 false9 <U>Optional<U> map(Function<? super T,? extends U> mapper) 如果存在該值,提供的映射方法,如果返回非null,返回一個Optional描述結果。 10 static <T> Optional<T> of(T value) 返回一個指定非null值的Optional。 11 static <T> Optional<T> ofNullable(T value) 如果為非空,返回 Optional 描述的指定值,否則返回空的 Optional。 12 T orElse(T other) 如果存在該值,返回值, 否則返回 other。 13 T orElseGet(Supplier<? extends T> other) 如果存在該值,返回值, 否則觸發 other,並返回 other 調用的結果。 14 <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) 如果存在該值,返回包含的值,否則拋出由 Supplier 繼承的異常 15 String toString() 返回一個Optional的非空字符串,用來調試
View Code

註意: 這些方法是從 java.lang.Object 類繼承來的。

4、示例

import java.util.Optional;
 
public class Java8Tester {
   public static void main(String args[]){
   
      Java8Tester java8Tester = new Java8Tester();
      Integer value1 = null;
      Integer value2 = new Integer(10);
        
      // Optional.ofNullable - 允許傳遞為 null 參數
      Optional<Integer> a = Optional.ofNullable(value1);
        
      // Optional.of - 如果傳遞的參數是 null,拋出異常 NullPointerException
      Optional<Integer> b = Optional.of(value2);
      System.out.println(java8Tester.sum(a,b));
   }
    
   public Integer sum(Optional<Integer> a, Optional<Integer> b){
    
      // Optional.isPresent - 判斷值是否存在
        
      System.out.println("第一個參數值存在: " + a.isPresent());
      System.out.println("第二個參數值存在: " + b.isPresent());
        
      // Optional.orElse - 如果值存在,返回它,否則返回默認值
      Integer value1 = a.orElse(new Integer(0));
        
      //Optional.get - 獲取值,值需要存在
      Integer value2 = b.get();
      return value1 + value2;
   }
}

輸出

第一個參數值存在: false
第二個參數值存在: true
10

1.6、Stream流

1、定義

Stream(流)是一個來自數據源的元素隊列並支持聚合操作

  • <strong元素隊列< strong="">元素是特定類型的對象,形成一個隊列。 Java中的Stream並不會存儲元素,而是按需計算。
  • 數據源 流的來源。 可以是集合,數組,I/O channel, 產生器generator 等。
  • 聚合操作 類似SQL語句一樣的操作, 比如filter, map, reduce, find, match, sorted等。

和以前的Collection操作不同, Stream操作還有兩個基礎的特征:

  • Pipelining: 中間操作都會返回流對象本身。 這樣多個操作可以串聯成一個管道, 如同流式風格(fluent style)。 這樣做可以對操作進行優化, 比如延遲執行(laziness)和短路( short-circuiting)。
  • 內部叠代: 以前對集合遍歷都是通過Iterator或者For-Each的方式, 顯式的在集合外部進行叠代, 這叫做外部叠代。 Stream提供了內部叠代的方式, 通過訪問者模式(Visitor)實現。

由於操作過程完全由Java處理,因此它可以根據當前硬件環境選擇最優的方法處理,我們也無需編寫復雜又容易出錯的多線程代碼了。

2、生成流

在 Java 8 中, 集合接口有兩個方法來生成流:

  • stream() − 為集合創建串行流。

  • parallelStream() − 為集合創建並行流。

3、流的操作種類

流的操作分為兩種,分別為中間操作 和 終端操作。
中間操作
  當數據源中的數據上了流水線後,這個過程對數據進行的所有操作都稱為“中間操作”。
  中間操作仍然會返回一個流對象,因此多個中間操作可以串連起來形成一個流水線。
  中間操作都是filter()、distinct()、sorted()、map()、flatMap()等,其一般是對數據集的整理(過濾、排序、匹配、抽取等)
終端操作
  當所有的中間操作完成後,若要將數據從流水線上拿下來,則需要執行終端操作。
  終端操作將返回一個執行結果,這就是你想要的數據。
  終止方法往往是完成對數據集中數據的處理,如forEach(),還有allMatch()、anyMatch()、findAny()、 findFirst(),數值計算類的方法有sum、max、min、average等等。終止方法也可以是對集合的處理,如reduce()、 collect()等等。reduce()方法的處理方式一般是每次都產生新的數據集,而collect()方法是在原數據集的基礎上進行更新,過程中不產生新的數據集。

4、流的操作過程

使用流一共需要三步:
準備一個數據源

技術分享圖片
1、集合 
這種數據源較為常用,通過stream()方法即可獲取流對象:
List<Person> list = new ArrayList<Person>(); 
Stream<Person> stream = list.stream();
2、數組 
通過Arrays類提供的靜態函數stream()獲取數組的流對象:
String[] names = {"chaimm","peter","john"};
Stream<String> stream = Arrays.stream(names);
3.直接將幾個值變成流對象:
Stream<String> stream = Stream.of("chaimm","peter","john");
4、文件 
try(Stream lines = Files.lines(Paths.get(“文件路徑名”),Charset.defaultCharset())){ 
//可對lines做一些操作 
}catch(IOException e){ 
} 
PS:Java7簡化了IO操作,把打開IO操作放在try後的括號中即可省略關閉IO的代碼。
View Code

執行中間操作 【中間操作可以有多個,它們可以串連起來形成流水線。】、執行終端操作 【執行終端操作後本次流結束,你將獲得一個執行結果。】

5、流轉換為其他數據結構

// 1. Array
String[] strArray1 = stream.toArray(String[]::new);
// 2. Collection【list,set,stack】
List<String> list1 = stream.collect(Collectors.toList());
List<String> list2 = stream.collect(Collectors.toCollection(ArrayList::new));
Set set1 = stream.collect(Collectors.toSet());
Stack stack1 = stream.collect(Collectors.toCollection(Stack::new));
// 3. String
String str = stream.collect(Collectors.joining()).toString();

6、使用

6.1、篩選filter

filter函數接收一個Lambda表達式作為參數,該表達式返回boolean,在執行過程中,流將元素逐一輸送給filter,並篩選出執行結果為true的元素。

List<Person> result = list.stream()
                    .filter(p->p.getIsStudent())
                    .collect(toList());

6.2、去重distinct

返回一個有唯一元素的stream(根據stream中元素的equals實現)。內部需要重寫equals

List<Person> result = list.stream()
                    .distinct()
                    .collect(toList());

6.3、截取limit

截取流的前N個元素:

List<Person> result = list.stream()
                    .limit(3)
                    .collect(toList());

6.4、跳過【skip】

跳過流的前n個

List<Person> result = list.stream()
                    .skip(3)
                    .collect(toList());

6.5、映射【map】

對流中的每個元素執行一個函數,使得元素轉換成另一種類型輸出。流會將每一個元素輸送給map函數,並執行map中的Lambda表達式,最後將執行結果存入一個新的流中。

final List<Integer> numbers = Arrays.asList(1, 2, 3, 4);
final List<Integer> doubleNumbers = numbers.stream().map(number -> number * 2).collect(Collectors.toList());
System.out.println(doubleNumbers);

6.6、合並【flagmap】

List<String> list = new ArrayList<String>();
list.add("I am a boy");
list.add("I love the girl");
list.add("But the girl loves another girl");
list.stream()
            .map(line->line.split(" "))
            .flagmap(Arrays::stream)
            .distinct()
            .collect(toList());

6.7、排序【sorted】

不指定一個自定義的Comparator則會使用默認排序。

        result = list.stream()
                .sorted((a,b)->a.getAge().compareTo(b.getAge()))
                .collect(toList());

        result = list.stream()
                .sorted(Comparator.comparing(Person::getAge))
                .collect(toList());

6.8、是否匹配任意一個元素anyMatch

anyMatch用於判斷流中是否存在至少一個元素滿足指定的條件,這個判斷條件通過Lambda表達式傳遞給anyMatch,執行結果為boolean類型。

//如,判斷list中是否有學生:
boolean result = list.stream().anyMatch(Person::isStudent);
//等價於
boolean result = list.stream().anyMatch(p->p.getIsStudent());

6.9、是否匹配所有元素:allMatch

allMatch用於判斷流中的所有元素是否都滿足指定條件,這個判斷條件通過Lambda表達式傳遞給anyMatch,執行結果為boolean類型。

如,判斷是否所有人都是學生:
boolean result = list.stream().allMatch(Person::isStudent);

6.10、是否未匹配所有元素:noneMatch

noneMatch與allMatch恰恰相反,它用於判斷流中的所有元素是否都不滿足指定條件:

boolean result = list.stream().noneMatch(Person::isStudent);

6.11、獲取任一元素findAny

findAny能夠從流中隨便選一個元素出來,它返回一個Optional類型的元素。

Optional<Person> person = list.stream().findAny();

6.12、獲取第一個元素

Optional<Person> person = list.stream().findFirst();

7、聚合操作-Stream.reduce

  Stream.reduce,常用的方法有average, sum, min, max, and count,返回單個的結果值,並且reduce操作每處理一個元素總是創建一個新值

  歸約是將集合中的所有元素經過指定運算,折疊成一個元素輸出,如:求最值、平均數等,這些操作都是將一個集合的元素折疊成一個元素輸出。
  在流中,reduce函數能實現歸約。

T reduce(T identity, BinaryOperatoraccumulator)
// identity:它允許用戶提供一個循環計算的初始值。accumulator:計算的累加器,其方法簽名為apply(T t,U u),在該reduce方法中第一個參數t為上次函數計算的返回值,
// 第二個參數u為Stream中的元素,這個函數把這兩個值計算apply,得到的和會被賦值給下次執行這個方法的第一個參數。有點繞看代碼:
int value = Stream.of(1, 2, 3, 4).reduce(100, (sum, item) -> sum + item); Assert.assertSame(value, 110); /* 或者使用方法引用 */ value = Stream.of(1, 2, 3, 4).reduce(100, Integer::sum); //這個例子中100即為計算初始值,每次相加計算值都會傳遞到下一次計算的第一個參數。

reduce還有其它兩個重載方法:
Optionalreduce(BinaryOperatoraccumulator):與上面定義基本一樣,無計算初始值,所以他返回的是一個Optional。
U reduce(U identity, BiFunction accumulator, BinaryOperator combiner):與前面兩個參數的reduce方法幾乎一致,你只要註意到BinaryOperator其實實現了BiFunction和BinaryOperator兩個接口。
Integer類還提供了min、max等一系列數值操作,當流中元素為數值類型時可以直接使用。

8、 聚合操作-Stream.collect

  收集器用來將經過篩選、映射的流進行最後的整理,可以使得最後的結果以不同的形式展現。
  流由一個個元素組成,歸約就是將一個個元素“折疊”成一個值,如求和、求最值、求平均值都是歸約操作。

Collectors
  toList()、toSet、joining、mapping、sum、age、group、max、min

8.1、count

        long count = list.stream().count();
        Long count2 = list.stream().collect(Collectors.counting());

8.2、max,min

        Optional<Person> personOptionalMax = list.stream().max(Comparator.comparing(p -> p.getAge()));
        Optional<Person> personOptionalMax1 = list.stream().collect(maxBy(Comparator.comparing(Person::getAge)));


        Optional<Person> personOptionalMin = list.stream().min(Comparator.comparing(p -> p.getAge()));
        Optional<Person> personOptionalMin1 = list.stream().collect(minBy(Comparator.comparing(Person::getAge)));

8.3、sum、avg

        Integer sum = list.stream().collect(summingInt(Person::getAge));
        Double avg = list.stream().collect(averagingInt(Person::getAge));

8.4、summarizingInt一次性計算所有歸約操作

Collectors.summarizingInt函數能一次性將最值、均值、總和、元素個數全部計算出來,並存儲在對象IntSummaryStatisics中。
可以通過該對象的getXXX()函數獲取這些值。

        DoubleSummaryStatistics dss = list.stream().collect(Collectors.summarizingDouble(p->p.getAge()));
        double average=dss.getAverage();
        double max=dss.getMax();
        double min=dss.getMin();
        double sum1=dss.getSum();
        double count1=dss.getCount();

8.5、Join

String names = list.stream().collect(Collectors.joining());
//每個字符串默認分隔符為空格,若需要指定分隔符,則在joining中加入參數即可:
String names = list.stream().collect(Collectors.joining(", "));

8.6、自定義規約

若你需要自定義一個歸約操作,那麽需要使用Collectors.reducing函數,該函數接收三個參數:
第一個參數為歸約的初始值
第二個參數為歸約操作進行的字段
第三個參數為歸約操作的過程

//例:計算所有人的年齡總和
Optional<Integer> sumAge = list.stream().collect(Collectors.reducing(0,Person::getAge,(i,j)->i+j));

Collectors.reducing方法還提供了一個單參數的重載形式。
你只需傳一個歸約的操作過程給該方法即可(即第三個參數),其他兩個參數均使用默認值。
第一個參數默認為流的第一個元素
第二個參數默認為流的元素
這就意味著,當前流的元素類型為數值類型,並且是你要進行歸約的對象。

//例:采用單參數的reducing計算所有人的年齡總和
Optional<Integer> sumAge = list.stream().filter(Person::getAge).collect(Collectors.reducing((i,j)->i+j));

8.7、結果收集

Collectors.toList()、Collectors.toMap、Collectors.toSet

註意:其中Collectors.toMap方法的第三個參數為鍵值重復處理策略,如果不傳入第三個參數,當有相同的鍵時,會拋出一個IlleageStateException。

8.8、分組和分區

對具有相同特性的值進行分組是一個很常見的任務,Collectors提供了一個groupingBy方法,方法簽名為:

Collector<T,?,Map> groupingBy(Function classifier, Collector downstream)

classifier:一個獲取Stream元素中主鍵方法。downstream:一個操作對應分組後的結果的方法。這裏可進行很多分組後的二次操作,如再次分組,統計等

//示例 根據年齡分組
Map<Integer, List<Person>> collect = list.stream().collect(Collectors.groupingBy(p -> p.getAge(), Collectors.toList()));

多級分組

        //先按照年齡分組,再分組內再次按照名稱分組
        Map<Integer, Map<String, List<Person>>> collect1 = list.stream().collect(
                Collectors.groupingBy(p -> p.getAge(), Collectors.groupingBy(t -> t.getName())));

分區是分組的一種特殊情況,它只能分成true、false兩組。

  分區使用partitioningBy方法,該方法接收一個Lambda表達式,該表達是必須返回boolean類型,partitioningBy方法會將Lambda返回結果為true和false的元素各分成一組。

  partitioningBy方法返回的結果為Map< Boolean,List< T>>。

  此外,partitioningBy方法和groupingBy方法一樣,也可以接收第二個參數,實現二級分區或對分區結果進行統計。

  groupingBy與partitioningBy,它們的區別是partitioningBy為鍵值為Boolean類型的groupingBy,這種情況下它比groupingBy更有效率。

9、數值流的使用

采用reduce進行數值操作會涉及到基本數值類型和引用數值類型之間的裝箱、拆箱操作,因此效率較低。 當流操作為純數值操作時,使用數值流能獲得較高的效率。

1、將普通流轉換成數值流
  StreamAPI提供了三種數值流:IntStream、DoubleStream、LongStream,也提供了將普通流轉換成數值流的三種方法:mapToInt、mapToDouble、mapToLong。
  如,將Person中的age轉換成數值流:

IntStream stream = list.stream().mapToInt(Person::getAge);

2 數值計算

  每種數值流都提供了數值計算函數,如max、min、sum等。
  如,找出最大的年齡:

OptionalInt maxAge = list.stream().mapToInt(Person::getAge).max();

  由於數值流可能為空,並且給空的數值流計算最大值是沒有意義的,因此max函數返回OptionalInt,它是Optional的一個子類,能夠判斷流是否為空,並對流為空的情況作相應的處理。
  此外,mapToInt、mapToDouble、mapToLong進行數值操作後的返回結果分別為:OptionalInt、OptionalDouble、OptionalLong

010-jdk1.8版本新特性二-Optional類,Stream流