天天看點

Java8新特性探索之Stream接口

一、為什麼引入Stream流

流是一系列與特定存儲機制無關的元素——實際上,流并沒有“存儲”之說。使用流,無需疊代集合中的元素,就可以從管道提取和操作元素。這些管道通常被組合在一起,形成一系列對流進行操作的管道。

在大多數情況下,将對象存儲在集合中是為了處理他們,是以你将會發現你将程式設計的主要焦點從集合轉移到了流上,流的一個核心的好處是,它使得程式更加短小并且更易了解。當Lambda表達式和方法引用和流一起使用的時候會讓人感覺自成一體。

二、如何使用Stream流

流操作的類型有三種:建立流,修改流元素(中間操作 Intermediate Operations),消費流元素(終端操作 Terminal Operations)

建立Stream流
  1. 使用

    Arrays.stream()

    方法建立
    Integer[] arr = new Integer[]{1,2,3,4,5};
    Arrays.stream(arr).filter(num -> num > 3);
               
  2. 使用

    Stream.of ()

    方法建立
    Integer[] arr = new Integer[]{1,2,3,4,5};
    Stream.of(arr).filter(num -> num > 3);
               
    檢視

    of()

    的源碼中得知,該方法也是調用了

    Arrays.stream()

    方法實作的
    /**
    * Returns a sequential ordered stream whose elements are the specified values.
    *
    * @param <T> the type of stream elements
    * @param values the elements of the new stream
    * @return the new stream
    */
    @SafeVarargs
    @SuppressWarnings("varargs") // Creating a stream from an array is safe
    public static<T> Stream<T> of(T... values) {
    	return Arrays.stream(values);
    }
               
  3. 使用

    Collection.stream()

    方法建立
    List<String> list = new ArrayList<>(1);
    list.stream().forEach(str -> System.out.println(str));
               
  4. 使用

    Stream.iterate()

    方法建立
    Stream.iterate(1, num -> num + 2).limit(10).forEach(num -> System.out.println(num));
               
  5. 使用

    Stream.generate()

    方法建立
    Stream.generate(() -> Arrays.asList(arr)).limit(1).forEach(num -> System.out.println(num));
               
修改流元素(中間操作 Intermediate Operations)

中間操作用于從一個流中擷取對象,并将對象作為另一個流從後端輸出,以連接配接到其他操作。

1、跟蹤和調試

peek()

操作的目的是幫助調試,允許你無修改地檢視流中的元素

// streams/Peeking.java
class Peeking {
    public static void main(String[] args) throws Exception {
        FileToWords.stream("Cheese.dat")
        .skip(21)
        .limit(4)
        .map(w -> w + " ")
        .peek(System.out::print)
        .map(String::toUpperCase)
        .peek(System.out::print)
        .map(String::toLowerCase)
        .forEach(System.out::print);
    }
}
           

輸出結果:

Well WELL well it IT it s S s so SO so
           

因為

peek()

符合無傳回值的 Consumer 函數式接口,是以我們隻能觀察,無法使用不同的元素來替換流中的對象。

2、流元素排序

sorted()

方法是需要周遊整個流的,并在産生任何元素之前對它進行排序。因為有可能排序後集合的第一個元素會在未排序集合的最後一位。

@Test
public void sortedTest() {
    List<Integer> numList = Lists.newArrayList();
    numList.add(8);
    numList.add(2);
    numList.add(6);
    numList.add(9);
    numList.add(1);
    List<Integer> sortList = numList.stream().sorted(Integer::compareTo).collect(Collectors.toList());
    System.out.println(sortList);
}
           

輸出結果:

[1, 2, 6, 8, 9]
           
3、移除元素
  • distinct()

    可用于消除流中的重複元素。相比建立一個 Set 集合,該方法的工作量要少得多。
    @Test
    public void distinctTest() {
        Stream.of(6, 8, 9, 6, 2, 8).distinct().forEach(i -> System.out.print(i + ", "));
    }
               
    輸出結果:
    6, 8, 9, 2, 
               
  • filter(Predicate)

    :若元素傳遞給過濾函數産生的結果為

    true

    ,則過濾操作保留這些元素。
    @Test
    public void filterTest() {
    	Stream.of(6, 9, 2, 8).filter(num -> num > 5).sorted().forEach(i -> System.out.print(i + ", "));
    }
               
    輸出結果:
    6, 8, 9, 
               
4、映射,應用函數到元素
  • map(Function)

    :将函數操作應用在輸入流的元素中,對一個流中的值進行某種形式的轉換,并将傳回值傳遞到輸出流中
    @Test
    public void mapTest() {
    	Stream.of("abc", "qw", "mnkh").map(String::length).forEach(n -> System.out.format("%d ", n));
    }
               
    輸出結果:
    3 2 4 
               
  • mapToInt(ToIntFunction)

    :操作同上,但結果是

    IntStream

    Stream.of("5", "7", "9").mapToInt(Integer::parseInt).forEach(n -> System.out.format("%d ", n));
               
  • mapToLong(ToLongFunction)

    :操作同上,但結果是

    LongStream

    Stream.of("17", "19", "23").mapToLong(Long::parseLong).forEach(n -> System.out.format("%d ", n));
               
  • mapToDouble(ToDoubleFunction)

    :操作同上,但結果是

    DoubleStream

    Stream.of("17", "1.9", ".23").mapToDouble(Double::parseDouble).forEach(n -> System.out.format("%f ", n));
               
  • flatMap()

    做了兩件事:将産生流的函數應用在每個元素上(與

    map()

    所做的相同),然後将每個流都扁平化為元素,因而最終産生的僅僅是元素。
    List<Integer> listA = Lists.newArrayList();
    listA.add(1);
    listA.add(6);
    List<Integer> listB = Lists.newArrayList();
    listB.add(10);
    listB.add(2);
    Map<String, List<Integer>> abMap = Maps.newHashMap();
    abMap.put("A", listA);
    abMap.put("B", listB);
    // 需擷取A和B集合中大于5的元素
    abMap.values().stream().flatMap(num -> num.stream().filter(n -> n > 5)).collect(Collectors.toList())
        .forEach(System.out::println);
               
    輸出結果:
    6
    10
               
  • flatMapToInt(Function)

    :當

    Function

    産生

    IntStream

    時使用。
  • flatMapToLong(Function)

    :當

    Function

    産生

    LongStream

    時使用。
  • flatMapToDouble(Function)

    :當

    Function

    産生

    DoubleStream

    時使用。
5、集合流切片,可實作分頁
  • limit(n)

    方法會傳回一個包含n個元素的新的流(若總長小于n則傳回原始流)。
  • skip(n)

    方法正好相反,它會丢棄掉前面的n個元素。
    // 查詢第二頁的資料
    Integer pageNumber = 2;
    Integer pageSize = 10;
    Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12).skip((pageNumber - 1) * pageSize).limit(pageSize)
    				.forEach(System.out::println);
               
    輸出結果:
    11
    12
               
消費流元素(終端操作 Terminal Operations)

終端操作總是我們在流管道中所做的最後一件事,該操作将會擷取流的最終結果。

1、數組結果輸出
  • toArray()

    :将流轉換成适當類型的數組。
  • toArray(generator)

    :在特殊情況下,生成自定義類型的數組。
2、循環結果輸出
  • forEach(Consumer)

    常見如

    System.out::println

    作為 Consumer 函數。
  • forEachOrdered(Consumer)

    : 保證

    forEach

    在并行流處理時按照原始流順序操作。
    Arrays.stream(new Random(45).ints(0, 1000).limit(100).toArray()).limit(10).parallel().forEachOrdered(n -> 						System.out.format("%d ", n));
               
3、collect收集結果
  • collect(Collector)

    :使用 Collector 收集流元素到結果集合中。
  • collect(Supplier, BiConsumer, BiConsumer)

    :同上,第一個參數 Supplier 建立了一個新的結果集合,第二個參數 BiConsumer 将下一個元素收集到結果集合中,第三個參數 BiConsumer 用于将兩個結果集合合并起來。

    Collectorts

    類為我們提供了常用的收集類的各個工廠方法:
  1. 将一個流收集到一個List中可以這樣用
    Lists.newArrayList().stream().collect(Collectors.toList());
               
  2. 收集到Set中可以這樣用
    Lists.newArrayList().stream().collect(Collectors.toSet());
               
  3. 收集到Map中可以這樣用
    Lists.newArrayList(new User("Johnson", "重慶")).stream().collect(Collectors.toMap(User::getName, User::getAddress));
               
  4. 收集到Set時,控制Set的類型可以這樣用
    Lists.newArrayList().stream().collect(Collectors.toCollection(TreeSet::new));
               
  5. 将字流中的字元串連接配接并收集起來
    Lists.newArrayList().stream().collect(Collectors.joining(","));
               
  6. 各種聚合操作
    // 擷取流中的總和,平均值,最大值,最小值,一次性收集流中的結果
    List<Integer> listA = Lists.newArrayList(1, 2, 3, 4, 5);
    listA.stream().collect(Collectors.summingInt(Integer::intValue));
    listA.stream().collect(Collectors.averagingInt(Integer::intValue));
    listA.stream().collect(Collectors.maxBy(Integer::compareTo));
    listA.stream().collect(Collectors.minBy(Integer::compareTo));
    listA.stream().collect(Collectors.summarizingInt(Integer::intValue));
    // 分組分片,傳回結果:{"重慶渝北":[{"address":"重慶渝北","name":"Johnson"},{"address":"重慶渝北","name":"Jack"}],"重慶江北":[{"address":"重慶江北","name":"Tom"}]}
    List<User> listB = Lists.newArrayList(new User("Johnson", "重慶渝北"), new User("Tom", "重慶江北"), new User("Jack", "重慶渝北"));
    System.out.println(JSON.toJSONString(listB.stream().collect(Collectors.groupingBy(User::getAddress))));
               
4、組合流中元素
  • reduce(BinaryOperator)

    :使用 BinaryOperator 來組合所有流中的元素。因為流可能為空,其傳回值為 Optional
    // 結果為15
    System.out.println(Stream.of(1, 2, 3, 4, 5).reduce((x, y) -> x + y).get());
               
  • reduce(identity, BinaryOperator)

    :功能同上,但是使用 identity 作為其組合的初始值。是以如果流為空,identity 就是結果
    // 設定初始值為10則結果為25
    System.out.println(Stream.of(1, 2, 3, 4, 5).reduce(10, (x, y) -> x + y));
    // 集合流為空,則結果預設為初始值a
    List<String> list = Lists.newArrayList();
    System.out.println(list.stream().reduce("a", (x, y) -> x.length() > 1 ? x : y));
               
  • reduce(identity, BiFunction, BinaryOperator)

    :在串行流(stream)中,該方法跟第二個方法一樣,即第三個參數不會起作用。在并行流中,我們知道流被fork join出多個線程進行執行,此時每個線程的執行流程就跟第二個方法

    reduce(identity,BiFunction)

    一樣,而第三個參數

    BinaryOperator

    函數,則是将每個線程的執行結果當成一個新的流,然後使用第一個方法

    reduce(BinaryOperator)

    流程進行規約
    // 第三個參數在并行流中起效,将每個線程的執行結果當成一個新的流
    List<Integer> listA = Lists.newArrayList(1, 2, 3, 4, 5);
    // 串行流運作結果:15
    System.out.println(listA.stream().reduce(0, (x, y) -> x + y, (i, j) -> i * j));
    // 并行流運作結果:120
    System.out.println(listA.parallelStream().reduce(0, (x, y) -> x + y, (i, j) -> i * j));
               
5、流中元素比對
  • allMatch(Predicate)

    :如果流的每個元素提供給 Predicate 都傳回 true ,結果傳回為 true。在第一個 false 時,則停止執行計算。
    // 數組中第一個元素小于2,則停止比對傳回結果:flase
    System.out.println(Stream.of(1, 2, 3, 4, 5).allMatch(n -> n > 2));
    // 數組中所有元素都大于0,則停止比對傳回結果:true
    System.out.println(Stream.of(1, 2, 3, 4, 5).allMatch(n -> n > 0));
               
  • anyMatch(Predicate)

    :如果流的任意一個元素提供給 Predicate 傳回 true ,結果傳回為 true。在第一個 true 是停止執行計算。
    // 數組中第三個元素大于2,則停止比對傳回結果:true
    System.out.println(Stream.of(1, 2, 3, 4, 5).anyMatch(n -> n > 2));
               
  • noneMatch(Predicate)

    :如果流的每個元素提供給 Predicate 都傳回 false 時,結果傳回為 true。在第一個 true 時停止執行計算。
    // 數組中第三個元素大于2,則停止比對傳回結果:true
    System.out.println(Stream.of(1, 2, 3, 4, 5).noneMatch(n -> n > 2));
               
6、流中元素查找
  • findFirst()

    :傳回第一個流元素的 Optional,如果流為空傳回 Optional.empty。
    // 根據條件過濾後取第一個元素
    System.out.println(Stream.of(1, 2, 3, 4, 5).filter(n -> n > 2).findFirst().get());
               
  • findAny()

    :傳回含有任意流元素的 Optional,如果流為空傳回 Optional.empty。
    // 根據條件過濾後找到任何一個所比對的元素,就傳回,此方法在對流并行執行時效率高
    System.out.println(Stream.of(1, 2, 3, 4, 5).parallel().filter(n -> n > 2).findAny().get());
               
7、收集流資訊
  • count()

    :流中的元素個數。
  • max(Comparator)

    :根據所傳入的 Comparator 所決定的“最大”元素。
  • min(Comparator)

    :根據所傳入的 Comparator 所決定的“最小”元素。
  • average()

    :求取流元素平均值。
  • sum()

    :對所有流元素進行求和。
  • summaryStatistics()

    :生成有關此流元素的各種摘要資料。
    // 擷取流中元素數量,傳回結果:5
    System.out.println(Stream.of(1,2,3,4,5).count());
    // 擷取流中最大值,傳回結果:5
    System.out.println(Stream.of(1,2,3,4,5).max(Integer::compareTo).get());
    // 擷取流中最小值,傳回結果:1
    System.out.println(Stream.of(1,2,3,4,5).min(Integer::compareTo).get());
    // 擷取流中元素平均值,傳回結果:3.0
    System.out.println(Stream.of(1,2,3,4,5).mapToInt(Integer::intValue).average().getAsDouble());
    // 擷取流中各種摘要資料,傳回結果:IntSummaryStatistics{count=5, sum=15, min=1, average=3.000000, max=5}
    System.out.println(Stream.of(1,2,3,4,5).mapToInt(Integer::intValue).summaryStatistics());
    // 擷取流中元素總和,傳回結果:15
    System.out.println(Stream.of(1,2,3,4,5).mapToInt(Integer::intValue).sum());
               

三、總結

流式操作改變并極大地提升了 Java 語言的可程式設計性,并可能極大地阻止了 Java 程式設計人員向諸如 Scala 這種函數式語言的流轉。