Java8-Stream API

了解Stream

? Java8中有兩個(gè)最為重要的改變,一個(gè)是Lambda表達(dá)式,另一個(gè)就是Stream API,針對(duì)常見的集合數(shù)據(jù)處理,Stream API 提供了一種高效且易于使用的數(shù)據(jù)處理方式。

什么是Stream

基本概念

? 流(Stream)用于操作數(shù)據(jù)源所生成的元素序列。Java 8給Collection接口增加了兩個(gè)默認(rèn)方法,它們可以返回一個(gè)Stream

default Stream<E> stream() {
? return StreamSupport.stream(spliterator(), false);
}//stream()返回的是一個(gè)順序流

default Stream<E> parallelStream() {
? return StreamSupport.stream(spliterator(), true);
}//parallelStream()返回的是一個(gè)并發(fā)流

  1. Stream 自己不會(huì)存儲(chǔ)元素。
  2. Stream 不會(huì)改變?cè)磳?duì)象。相反,他們會(huì)返回一個(gè)持有結(jié)果的新Stream。
  3. Stream 操作是延遲執(zhí)行的。這意味著他們會(huì)等到需要結(jié)果的時(shí)候才執(zhí)行。

基本示例

首先這里有一個(gè)Employee類

public class Employee {
   private int id;
   private String name;
   private int age;
   private double salary;
   /*省略getter setter Constructor*/
}
//Employee列表
List<Employee> emps = Arrays.asList(
      new Employee(102, "李四", 59, 6666.66),
      new Employee(101, "張三", 18, 9999.99),
      new Employee(103, "王五", 28, 3333.33),
      new Employee(104, "趙六", 20, 7777.77),
      new Employee(104, "趙六", 19, 7777.77),
      new Employee(104, "趙四", 40, 7777.77),
      new Employee(105, "田七", 38, 5555.55)
);

返回薪資大于5000的員工列表,java8以前是這樣做的

List<Employee> newEmps = new ArrayList<>();
for(Employee emp : emps){
  if(emp.salary > 5000.00){
    newEmps.add(emp);
  }
}

使用Stream API ,代碼可以這樣

List<Employee> newEmps = emps.stream()
        .filter(s -> s.getSalary() > 5000.00)
        .collect(Collectors.toList());

先通過stream()得到一個(gè)Stream對(duì)象,然后調(diào)用Stream上的方法,filter()過濾得到薪資大于5000的,它的返回值依然是一個(gè)Stream,然后通過調(diào)用collect()方法并傳遞一個(gè)Collectors.toList()將結(jié)果集存放到一個(gè)List中.

使用Stream API處理集合類代碼更加簡(jiǎn)潔易讀.

下面介紹一下Stream中的兩種操作

Stream的中間操作和終止操作

中間操作:

? 多個(gè)中間操作可以連接起來(lái)形成一個(gè)流水線,除非流水線上觸發(fā)終止操作,否則中間操作不會(huì)執(zhí)行任何的處理!而在終止操作時(shí)一次性全部處理,稱為“惰性求值”

方法 描述
filter(Predicate p) 接收 Lambda , 從流中排除某些元素。
distinct() 篩選,通過流所生成元素的 hashCode() 和 equals() 去
limit(long maxSize) 截?cái)嗔鳎蛊湓夭怀^給定數(shù)量。
map(Function f) 接收一個(gè)函數(shù)作為參數(shù),該函數(shù)會(huì)被應(yīng)用到每個(gè)元素上,并將其映射成一個(gè)新的元素。
flatMap(Function f) 接收一個(gè)函數(shù)作為參數(shù),將流中的每個(gè)值都換成另一個(gè)流,然后把所有流連接成一個(gè)流
sorted(Comparator comp) 產(chǎn)生一個(gè)新流,其中按比較器順序排序
sorted() 產(chǎn)生一個(gè)新流,其中按自然順序排序

終止操作:

? 終端操作會(huì)從流的流水線生成結(jié)果。其結(jié)果可以是任何不是流的值,例如:List、Integer,甚至是void 。

方法 描述
forEach(Consumer c) 內(nèi)部迭代
collect(Collector c) 將流轉(zhuǎn)換為其他形式。接收一個(gè) Collector接口的實(shí)現(xiàn),用于給Stream中元素做匯總的方法
max(Comparator c) 返回流中最大值
min(Comparator c) 返回流中最小值
count() 返回流中元素總數(shù)

收集 : collect(Collector c)方法需要一個(gè)Collector 作為參數(shù),Collector 接口中方法的實(shí)現(xiàn)決定了如何對(duì)流執(zhí)行收集操作(如收集到 List、Set、Map)。Java8中提供了一個(gè)Collectors工具類, 工具中提供了很多靜態(tài)方法,可以方便地創(chuàng)建常見收集器例

具體方法與實(shí)例如下表

方法 返回類型 作用
toList List<T> 把流中元素收集到List
toSet Set<T> 把流中元素收集到Set
toCollection Collection<T> 把流中元素收集到創(chuàng)建的集合
groupingBy Map<K, List<T>> 根據(jù)某屬性值對(duì)流分組,屬性為K,結(jié)果為V
partitioningBy Map<Boolean, List<T>> 根據(jù)true或false進(jìn)行分區(qū)

這里只列出了一些常用的方法.具體參考Java8 Stream API : Java Platform SE 8

Stream API 使用

中間操作

  1. 映射(map/flatMap)

    map——接收 Lambda , 將元素轉(zhuǎn)換成其他形式或提取信息。接收一個(gè)函數(shù)作為參數(shù),該函數(shù)會(huì)被應(yīng)用到每個(gè)元素上,并將其映射成一個(gè)新的元素。

    <R> Stream<R> map(Function<? super T, ? extends R> mapper);

    map操作會(huì)將流里的每個(gè)元素按mapper轉(zhuǎn)換后的結(jié)果添加到一個(gè)新流中.

    List<String> list = Arrays.asList("1,2", "3,4");
    //每次mapper操作返回一個(gè)數(shù)組,將每個(gè)數(shù)組添加到新流中,最終生成Stream<String[]>
    Stream<String[]> stream = list.stream().map(s -> s.split(","));
    //每次mapper操作返回一個(gè)流Stream<String>,將每個(gè)流添加到新流中,最終生成Stream<Stream<String>>
    Stream<Stream<String>> streamStream = list.stream().map(s -> Arrays.stream(s.split(",")));
    

    flatMap——接收一個(gè)函數(shù)作為參數(shù),將流中的每個(gè)值都換成另一個(gè)流,然后把所有流連接成一個(gè)流

    <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)

    它接受一個(gè)函數(shù)mapper,對(duì)流中的每一個(gè)元素,mapper會(huì)將該元素轉(zhuǎn)換為一個(gè)流Stream,然后把新生成流的每一個(gè)元素傳遞給下一個(gè)操作.

    List<String> list = Arrays.asList("1,2", "3,4");
    //每次mapper操作返回一個(gè)流Stream<String> 然后將流里的每個(gè)元素添加到新流中,最終生成Stream<String>
    Stream<String> stringStream = list.stream().flatMap(s -> Arrays.stream(s.split(",")));
    

    flatMap 把 Stream 中的層級(jí)結(jié)構(gòu)扁平化,就是將最底層元素抽出來(lái)放到一起,最終生成的新 Stream 里面都是直接的字符串。

  2. 排序(sort)

    sorted() ——自然排序(根據(jù)流中元素實(shí)現(xiàn)的Comparable接口的compareTo()方法來(lái)排序的)

    sorted(Comparator com) ——定制排序(根據(jù)特定的比較器來(lái)排序)

    List<Integer> list = Arrays.asList(1,3,4,0,9,8,5);
    Stream<Integer> sorted = list.stream().sorted();
    sorted.forEach(System.out::print);
    /*
     輸出結(jié)果: 0134589
    */
    
    emps.stream()
            .sorted((x, y) -> {
                if (x.getAge() == y.getAge()) {
                    return x.getName().compareTo(y.getName());
                } else {
                    return Integer.compare(x.getAge(), y.getAge());
                }
            }).forEach(System.out::println);
    /*
     指定比較規(guī)則,按姓名排序,姓名相同的再根據(jù)年齡排序
    */
    
  3. 篩選與切片

    filter : 接受Lambda,從流中排除某些元素

    limit(n) : 返回流中前n個(gè)元素

    skip(n) : 跳過流中前n個(gè)元素

    distinct : 去掉流中重復(fù)元素(通過hashCode和equles方法判斷是否為相同對(duì)象)

    filter

    // 篩選出姓趙的員工
    Stream<Employee> resultStream = emps.stream()
            .filter(employee ->
                    employee.getName().startsWith("趙"));
    resultStream.forEach(System.out::println);
    /*
     輸出結(jié)果:
     Employee [id=104, name=趙六, age=20, salary=7777.77]
     Employee [id=104, name=趙六, age=19, salary=7777.77]
     Employee [id=104, name=趙四, age=40, salary=7777.77]
    */
    

    limit

    //獲取列表前3個(gè)員工
    Stream<Employee> limit = emps.stream().limit(3);
    limit.forEach(System.out::println);
    /*
     Employee [id=102, name=李四, age=59, salary=6666.66]
     Employee [id=101, name=張三, age=18, salary=9999.99]
     Employee [id=103, name=王五, age=28, salary=3333.33]
    */
    

    skin

    //去掉前3個(gè)員工
    Stream<Employee> limit = emps.stream().skip(3);
    limit.forEach(System.out::println);
    /*
     Employee [id=104, name=趙六, age=20, salary=7777.77]
     Employee [id=104, name=趙六, age=19, salary=7777.77]
     Employee [id=104, name=趙四, age=40, salary=7777.77]
     Employee [id=105, name=田七, age=38, salary=5555.55]
    */
    

    distinct

    List<Integer> list = Arrays.asList(1, 1, 2, 2, 3, 3, 3, 3, 4, 5, 6);
    Stream<Integer> distinct = list.stream().distinct();//去掉重復(fù)元素
    distinct.forEach(System.out::print);
    /*
     123456
    */
    

終止操作

  1. 查找與匹配

allMatch——檢查是否匹配所有元素
anyMatch——檢查是否至少匹配一個(gè)元素
noneMatch——檢查是否沒有匹配的元素
findFirst——返回第一個(gè)元素
findAny——返回當(dāng)前流中的任意元素
count——返回流中元素的總個(gè)數(shù)
max——返回流中最大值
min——返回流中最小值

allMatch

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6);
boolean b = list.stream().allMatch(i -> i < 10);//檢查所有元素是否都小于10
//true

anyMatch

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6);
boolean b = list.stream().anyMatch(i -> i < 2);//檢查是否至少有一個(gè)元素小于2
//true

noneMatch

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6);
boolean b = list.stream().noneMatch(i -> i < 2);//檢查是否沒有一個(gè)元素小于2
//false

findFirst

//返回list第一個(gè)元素
Optional<Integer> any = list.stream().findFirst();
System.out.println(any.get());

Optional<T> 類是一個(gè)是一個(gè)容器類,代表一個(gè)值存在或不存在,原來(lái)用 null 表示一個(gè)值不存在,現(xiàn)在Optional<T>可以更好的表達(dá)這個(gè)概念,并且可以避免空指針異常

這里findFirst()查找第一個(gè)元素有可能為空,就把結(jié)果封裝成一個(gè)Optional類.

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6);
long count = list.stream().count();//統(tǒng)計(jì)元素個(gè)數(shù)
System.out.println(count);//6
Optional<Integer> max = list.stream().max(Integer::compareTo);//最大值
System.out.println(max.get());//6
Optional<Integer> min = list.stream().min(Integer::compareTo);//最小值
System.out.println(min.get());//1
  1. 規(guī)約(reduce)

    `reduce(T identity, BinaryOperator bo) / reduce(BinaryOperator) ——可以將流中元素按照指定的二院運(yùn)算反復(fù)結(jié)合起來(lái),得到一個(gè)值

    identity : 起始值

    BinaryOperator : 二元運(yùn)算

    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);
    /*
     輸出結(jié)果 55
    */
    

    這里reduce操作中,起始值為0,第一次x為0,list中第一個(gè)元素1為y 經(jīng)行(x+y)操作,然后又把(x+y)的值作為x, list中第二個(gè)元素2作為y,依次累加.最終得到一個(gè)sum值

    Optional<Double> op = emps.stream()
       .map(Employee::getSalary)
       .reduce(Double::sum);//計(jì)算所有員工薪資總和
    System.out.println(op.get());
    

    這個(gè)地方由于沒有初始值,計(jì)算結(jié)果可能為空(列表為空的情況),所以就把計(jì)算結(jié)果封裝到Optional中避免空指針異常

  2. 收集(collect)

collect——將流轉(zhuǎn)換為其他形式。接收一個(gè) Collector接口的實(shí)現(xiàn),用于給Stream中元素做匯總的方法

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

//收集員工姓名到List集合
List<String> list = emps.stream()
   .map(Employee::getName)
   .collect(Collectors.toList());
list.forEach(System.out::print);
// 輸出: 李四張三王五趙六趙六趙六田七
//收集員工姓名到Set集合
Set<String> set = emps.stream()
   .map(Employee::getName)
   .collect(Collectors.toSet());
set.forEach(System.out::println);
// 輸出 : 李四張三王五趙六田七
//------------------------------------
HashSet<String> hs = emps.stream()
    .map(Employee::getName)
    .collect(Collectors.toCollection(HashSet::new));
hs.forEach(System.out::println);
// 輸出 : 李四張三王五趙六田七
  1. 分組

groupingBy : 根據(jù)指定的元素對(duì)流中數(shù)據(jù)進(jìn)行分組

//按照員工姓氏分組,這里不考慮復(fù)姓
Map<String, List<Employee>> collect = emps.stream()
        .collect(Collectors
                .groupingBy(emp -> String.valueOf(emp.getName().charAt(0))));
collect.forEach((k, v) -> {
    System.out.println(k + ":" + v);
});

輸出結(jié)果為:

田:[Employee [id=105, name=田七, age=38, salary=5555.55]

張:[Employee [id=101, name=張三, age=18, salary=9999.99]

趙:[Employee [id=104, name=趙六, age=20, salary=7777.77],

? Employee [id=104, name=趙六, age=19, salary=7777.77],

? Employee [id=104, name=趙四, age=40, salary=7777.77]]

王:[Employee [id=103, name=王五, age=28, salary=3333.33]
李:[Employee [id=102, name=李四, age=59, salary=6666.66]

  1. 分區(qū)

partitioningBy : 按照給定條件對(duì)流中元素進(jìn)行分區(qū)

//將員工以薪資6000.00為界限分區(qū)
Map<Boolean, List<Employee>> collect = emps.stream()
        .collect(Collectors.partitioningBy(e -> e.getSalary() > 6000.00));
collect.forEach((k, v) -> {
    System.out.println(k + ":" + v);
});

輸出結(jié)果為:

false:[Employee [id=103, name=王五, age=28, salary=3333.33],

? Employee [id=105, name=田七, age=38, salary=5555.55]]

true:[Employee [id=102, name=李四, age=59, salary=6666.66],

? Employee [id=101, name=張三, age=18, salary=9999.99],

? Employee [id=104, name=趙六, age=20, salary=7777.77],

? Employee [id=104, name=趙六, age=19, salary=7777.77],

? Employee [id=104, name=趙四, age=40, salary=7777.77]]

Optional 類

介紹

Optional 容器類:用于盡量避免空指針異常

方法

Optional 容器類:用于盡量避免空指針異常

Optional.of(T t) : 創(chuàng)建一個(gè) Optional 實(shí)例

Optional.empty() : 創(chuàng)建一個(gè)空的 Optional 實(shí)例

Optional.ofNullable(T t):若 t 不為 null,創(chuàng)建 Optional 實(shí)例,否則創(chuàng)建空實(shí)例

isPresent() : 判斷是否包含值

orElse(T t) : 如果調(diào)用對(duì)象包含值,返回該值,否則返回t

orElseGet(Supplier s) :如果調(diào)用對(duì)象包含值,返回該值,否則返回 s 獲取的值

map(Function f): 如果有值對(duì)其處理,并返回處理后的Optional,否則返回 Optional.empty()

flatMap(Function mapper):與 map 類似,要求返回值必須是Optional

小結(jié)

Stream 是 Java8 中處理集合的關(guān)鍵抽象概念,它可以指定你希望對(duì)集合進(jìn)行的操作,可以執(zhí)行非常復(fù)雜的查找、過濾和映射數(shù)據(jù)等操作。使用Stream API 對(duì)集合數(shù)據(jù)進(jìn)行操作,就類似于使用 SQL 執(zhí)行的數(shù)據(jù)庫(kù)查詢。也可以使用 Stream API 來(lái)并行執(zhí)行操作。

簡(jiǎn)而言之,Stream API 提供了一種高效且易于使用的處理數(shù)據(jù)的方式

原文鏈接 : Java8-新特性-Stream | 火堯

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,981評(píng)論 19 139
  • 先看一段代碼 Stream API的歷史 在java8引入 受益于lambda表達(dá)式 lambda表達(dá)式 接口常被...
    ThomasYoungK閱讀 637評(píng)論 0 0
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類相關(guān)的語(yǔ)法,內(nèi)部類的語(yǔ)法,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法,線程的語(yǔ)...
    子非魚_t_閱讀 31,767評(píng)論 18 399
  • 一. Java基礎(chǔ)部分.................................................
    wy_sure閱讀 3,837評(píng)論 0 11
  • 一提起公務(wù)員這個(gè)職業(yè),很多人的第一反應(yīng)是:一張報(bào)紙一杯茶、混吃等死坐一天。嗯,一般有這反應(yīng)的大約都是沒有真正進(jìn)入過...
    一心一意做財(cái)女閱讀 1,680評(píng)論 2 5