【Java8新特性】面試官:談談Java8中的Stream API有哪些終止操作?

冰河團隊發表於2020-05-29

寫在前面

如果你出去面試,面試官問了你關於Java8 Stream API的一些問題,比如:Java8中建立Stream流有哪幾種方式?(可以參見:《【Java8新特性】面試官問我:Java8中建立Stream流有哪幾種方式?》)Java8中的Stream API有哪些中間操作?(可以參見:《【Java8新特性】Stream API有哪些中間操作?看完你也可以吊打面試官!!》)如果你都很好的回答了這些問題,那麼,面試官可能又會問你:Java8中的Stream API有哪些終止操作呢?沒錯,這就是Java8中有關Stream API的靈魂三問!不要覺得是面試官在為難你,只有你掌握了這些細節,你就可以反過來吊打面試官了!

Stream的終止操作

終端操作會從流的流水線生成結果。其結果可以是任何不是流的值,例如: List、 Integer、Double、String等等,甚至是 void 。

在Java8中,Stream的終止操作可以分為:查詢與匹配、規約和收集。接下來,我們就分別簡單說明下這些終止操作。

查詢與匹配

Stream API中有關查詢與匹配的方法如下表所示。

方法 描述
allMatch(Predicate p) 檢查是否匹配所有元素
anyMatch(Predicate p) 檢查是否至少匹配一個元素
noneMatch(Predicate p) 檢查是否沒有匹配所有元素
findFirst() 返回第一個元素
findAny() 返回當前流中的任意元素
count() 返回流中元素總數
max(Comparator c) 返回流中最大值
min(Comparator c) 返回流中最小值
forEach(Consumer c) 內部迭代(使用 Collection 介面需要使用者去做迭代,稱為外部迭代。相反, Stream API 使用內部迭代)

同樣的,我們對每個重要的方法進行簡單的示例說明,這裡,我們首先建立一個Employee類,Employee類的定義如下所示。

@Data
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Employee implements Serializable {
    private static final long serialVersionUID = -9079722457749166858L;
    private String name;
    private Integer age;
    private Double salary;
    private Stauts stauts;
    public enum Stauts{
        WORKING,
        SLEEPING,
        VOCATION
    }
}

接下來,我們在測試類中定義一個用於測試的集合employees,如下所示。

protected List<Employee> employees = Arrays.asList(
    new Employee("張三", 18, 9999.99, Employee.Stauts.SLEEPING),
    new Employee("李四", 38, 5555.55, Employee.Stauts.WORKING),
    new Employee("王五", 60, 6666.66, Employee.Stauts.WORKING),
    new Employee("趙六", 8, 7777.77, Employee.Stauts.SLEEPING),
    new Employee("田七", 58, 3333.33, Employee.Stauts.VOCATION)
);

好了,準備工作就緒了。接下來,我們就開始測試Stream的每個終止方法。

1.allMatch()

allMatch()方法表示檢查是否匹配所有元素。其在Stream介面中的定義如下所示。

boolean allMatch(Predicate<? super T> predicate);

我們可以通過類似如下示例來使用allMatch()方法。

boolean match = employees.stream().allMatch((e) -> Employee.Stauts.SLEEPING.equals(e.getStauts()));
System.out.println(match);

注意:使用allMatch()方法時,只有所有的元素都匹配條件時,allMatch()方法才會返回true。

2.anyMatch()方法

anyMatch方法表示檢查是否至少匹配一個元素。其在Stream介面中的定義如下所示。

boolean anyMatch(Predicate<? super T> predicate);

我們可以通過類似如下示例來使用anyMatch()方法。

boolean match = employees.stream().anyMatch((e) -> Employee.Stauts.SLEEPING.equals(e.getStauts()));
System.out.println(match);

注意:使用anyMatch()方法時,只要有任意一個元素符合條件,anyMatch()方法就會返回true。

3.noneMatch()方法

noneMatch()方法表示檢查是否沒有匹配所有元素。其在Stream介面中的定義如下所示。

boolean noneMatch(Predicate<? super T> predicate);

我們可以通過類似如下示例來使用noneMatch()方法。

boolean match = employees.stream().noneMatch((e) -> Employee.Stauts.SLEEPING.equals(e.getStauts()));
System.out.println(match);

注意:使用noneMatch()方法時,只有所有的元素都不符合條件時,noneMatch()方法才會返回true。

4.findFirst()方法

findFirst()方法表示返回第一個元素。其在Stream介面中的定義如下所示。

Optional<T> findFirst();

我們可以通過類似如下示例來使用findFirst()方法。

Optional<Employee> op = employees.stream().sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())).findFirst();
System.out.println(op.get());

5.findAny()方法

findAny()方法表示返回當前流中的任意元素。其在Stream介面中的定義如下所示。

Optional<T> findAny();

我們可以通過類似如下示例來使用findAny()方法。

Optional<Employee> op = employees.stream().filter((e) -> Employee.Stauts.WORKING.equals(e.getStauts())).findFirst();
System.out.println(op.get());

6.count()方法

count()方法表示返回流中元素總數。其在Stream介面中的定義如下所示。

long count();

我們可以通過類似如下示例來使用count()方法。

long count = employees.stream().count();
System.out.println(count);

7.max()方法

max()方法表示返回流中最大值。其在Stream介面中的定義如下所示。

Optional<T> max(Comparator<? super T> comparator);

我們可以通過類似如下示例來使用max()方法。

Optional<Employee> op = employees.stream().max((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
System.out.println(op.get());

8.min()方法

min()方法表示返回流中最小值。其在Stream介面中的定義如下所示。

Optional<T> min(Comparator<? super T> comparator);

我們可以通過類似如下示例來使用min()方法。

Optional<Double> op = employees.stream().map(Employee::getSalary).min(Double::compare);
System.out.println(op.get());

9.forEach()方法

forEach()方法表示內部迭代(使用 Collection 介面需要使用者去做迭代,稱為外部迭代。相反, Stream API 使用內部迭代)。其在Stream介面內部的定義如下所示。

void forEach(Consumer<? super T> action);

我們可以通過類似如下示例來使用forEach()方法。

employees.stream().forEach(System.out::println);

規約

Stream API中有關規約的方法如下表所示。

方法 描述
reduce(T iden, BinaryOperator b) 可以將流中元素反覆結合起來,得到一個值。 返回 T
reduce(BinaryOperator b) 可以將流中元素反覆結合起來,得到一個值。 返回 Optional

reduce()方法在Stream介面中的定義如下所示。

T reduce(T identity, BinaryOperator<T> accumulator);
Optional<T> reduce(BinaryOperator<T> accumulator);
<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner);

我們可以通過類似如下示例來使用reduce方法。

List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
Integer sum = list.stream().reduce(0, (x, y) -> x + y);
System.out.println(sum);
System.out.println("----------------------------------------");
Optional<Double> op = employees.stream().map(Employee::getSalary).reduce(Double::sum);
System.out.println(op.get());

我們也可以搜尋employees列表中“張”出現的次數。

 Optional<Integer> sum = employees.stream()
   .map(Employee::getName)
   .flatMap(TestStreamAPI1::filterCharacter)
   .map((ch) -> {
    if(ch.equals('六'))
     return 1;
    else
     return 0;
   }).reduce(Integer::sum);
  System.out.println(sum.get());

注意:上述例子使用了硬編碼的方式來累加某個具體值,大家在實際工作中再優化程式碼。

收集

方法 描述
collect(Collector c) 將流轉換為其他形式。接收一個 Collector介面的實現,用於給Stream中元素做彙總的方法

collect()方法在Stream介面中的定義如下所示。

<R> R collect(Supplier<R> supplier,
              BiConsumer<R, ? super T> accumulator,
              BiConsumer<R, R> combiner);

<R, A> R collect(Collector<? super T, A, R> collector);

我們可以通過類似如下示例來使用collect方法。

Optional<Double> max = employees.stream()
   .map(Employee::getSalary)
   .collect(Collectors.maxBy(Double::compare));
  System.out.println(max.get());
  Optional<Employee> op = employees.stream()
   .collect(Collectors.minBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));
  System.out.println(op.get());
  Double sum = employees.stream().collect(Collectors.summingDouble(Employee::getSalary));
  System.out.println(sum);
  Double avg = employees.stream().collect(Collectors.averagingDouble(Employee::getSalary));
  System.out.println(avg);
  Long count = employees.stream().collect(Collectors.counting());
  System.out.println(count);
  System.out.println("--------------------------------------------");
  DoubleSummaryStatistics dss = employees.stream()
   .collect(Collectors.summarizingDouble(Employee::getSalary));
  System.out.println(dss.getMax());

如何收集Stream流?

Collector介面中方法的實現決定了如何對流執行收集操作(如收集到 List、 Set、 Map)。 Collectors實用類提供了很多靜態方法,可以方便地建立常見收集器例項, 具體方法與例項如下表:

方法 返回型別 作用
toList List 把流中元素收集到List
toSet Set 把流中元素收集到Set
toCollection Collection 把流中元素收集到建立的集合
counting Long 計算流中元素的個數
summingInt Integer 對流中元素的整數屬性求和
averagingInt Double 計算流中元素Integer屬性的平均 值
summarizingInt IntSummaryStatistics 收集流中Integer屬性的統計值。 如:平均值
joining String 連線流中每個字串
maxBy Optional 根據比較器選擇最大值
minBy Optional 根據比較器選擇最小值
reducing 歸約產生的型別 從一個作為累加器的初始值 開始,利用BinaryOperator與 流中元素逐個結合,從而歸 約成單個值
collectingAndThen 轉換函式返回的型別 包裹另一個收集器,對其結 果轉換函式
groupingBy Map<K, List> 根據某屬性值對流分組,屬 性為K,結果為V
partitioningBy Map<Boolean, List> 根據true或false進行分割槽

每個方法對應的使用示例如下表所示。

方法 使用示例
toList List employees= list.stream().collect(Collectors.toList());
toSet Set employees= list.stream().collect(Collectors.toSet());
toCollection Collection employees=list.stream().collect(Collectors.toCollection(ArrayList::new));
counting long count = list.stream().collect(Collectors.counting());
summingInt int total=list.stream().collect(Collectors.summingInt(Employee::getSalary));
averagingInt double avg= list.stream().collect(Collectors.averagingInt(Employee::getSalary))
summarizingInt IntSummaryStatistics iss= list.stream().collect(Collectors.summarizingInt(Employee::getSalary));
Collectors String str= list.stream().map(Employee::getName).collect(Collectors.joining());
maxBy Optionalmax= list.stream().collect(Collectors.maxBy(comparingInt(Employee::getSalary)));
minBy Optional min = list.stream().collect(Collectors.minBy(comparingInt(Employee::getSalary)));
reducing int total=list.stream().collect(Collectors.reducing(0, Employee::getSalar, Integer::sum));
collectingAndThen int how= list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size));
groupingBy Map<Emp.Status, List> map= list.stream() .collect(Collectors.groupingBy(Employee::getStatus));
partitioningBy Map<Boolean,List>vd= list.stream().collect(Collectors.partitioningBy(Employee::getManage));
public void test4(){
    Optional<Double> max = emps.stream()
        .map(Employee::getSalary)
        .collect(Collectors.maxBy(Double::compare));
    System.out.println(max.get());

    Optional<Employee> op = emps.stream()
        .collect(Collectors.minBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));

    System.out.println(op.get());

    Double sum = emps.stream()
        .collect(Collectors.summingDouble(Employee::getSalary));

    System.out.println(sum);

    Double avg = emps.stream()
        .collect(Collecors.averagingDouble(Employee::getSalary));
    System.out.println(avg);
    Long count = emps.stream()
        .collect(Collectors.counting());

    DoubleSummaryStatistics dss = emps.stream()
        .collect(Collectors.summarizingDouble(Employee::getSalary));
    System.out.println(dss.getMax());
 

寫在最後

如果覺得文章對你有點幫助,請微信搜尋並關注「 冰河技術 」微信公眾號,跟冰河學習Java8新特性。

最後,附上Java8新特性核心知識圖,祝大家在學習Java8新特性時少走彎路。

相關文章