Java8-Stream API

了解Stream

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

什么是Stream

基本概念

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

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

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

  1. Stream 自己不會存儲元素。
  2. Stream 不會改變源對象帽芽。相反删掀,他們會返回一個持有結果的新Stream。
  3. Stream 操作是延遲執(zhí)行的导街。這意味著他們會等到需要結果的時候才執(zhí)行披泪。

基本示例

首先這里有一個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()得到一個Stream對象,然后調用Stream上的方法搬瑰,filter()過濾得到薪資大于5000的,它的返回值依然是一個Stream,然后通過調用collect()方法并傳遞一個Collectors.toList()將結果集存放到一個List中.

使用Stream API處理集合類代碼更加簡潔易讀.

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

Stream的中間操作和終止操作

中間操作:

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

方法 描述
filter(Predicate p) 接收 Lambda , 從流中排除某些元素佩厚。
distinct() 篩選姆钉,通過流所生成元素的 hashCode() 和 equals() 去
limit(long maxSize) 截斷流,使其元素不超過給定數(shù)量抄瓦。
map(Function f) 接收一個函數(shù)作為參數(shù)潮瓶,該函數(shù)會被應用到每個元素上,并將其映射成一個新的元素钙姊。
flatMap(Function f) 接收一個函數(shù)作為參數(shù)毯辅,將流中的每個值都換成另一個流,然后把所有流連接成一個流
sorted(Comparator comp) 產(chǎn)生一個新流煞额,其中按比較器順序排序
sorted() 產(chǎn)生一個新流思恐,其中按自然順序排序

終止操作:

? 終端操作會從流的流水線生成結果。其結果可以是任何不是流的值膊毁,例如:List胀莹、Integer,甚至是void 婚温。

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

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

具體方法與實例如下表

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

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

Stream API 使用

中間操作

  1. 映射(map/flatMap)

    map——接收 Lambda , 將元素轉換成其他形式或提取信息靡努。接收一個函數(shù)作為參數(shù)坪圾,該函數(shù)會被應用到每個元素上,并將其映射成一個新的元素惑朦。

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

    map操作會將流里的每個元素按mapper轉換后的結果添加到一個新流中.

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

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

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

    它接受一個函數(shù)mapper漾月,對流中的每一個元素,mapper會將該元素轉換為一個流Stream胃珍,然后把新生成流的每一個元素傳遞給下一個操作.

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

    flatMap 把 Stream 中的層級結構扁平化梁肿,就是將最底層元素抽出來放到一起,最終生成的新 Stream 里面都是直接的字符串觅彰。

  2. 排序(sort)

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

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

    List<Integer> list = Arrays.asList(1,3,4,0,9,8,5);
    Stream<Integer> sorted = list.stream().sorted();
    sorted.forEach(System.out::print);
    /*
     輸出結果: 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個元素

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

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

    filter

    // 篩選出姓趙的員工
    Stream<Employee> resultStream = emps.stream()
            .filter(employee ->
                    employee.getName().startsWith("趙"));
    resultStream.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]
    */
    

    limit

    //獲取列表前3個員工
    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個員工
    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();//去掉重復元素
    distinct.forEach(System.out::print);
    /*
     123456
    */
    

終止操作

  1. 查找與匹配

allMatch——檢查是否匹配所有元素
anyMatch——檢查是否至少匹配一個元素
noneMatch——檢查是否沒有匹配的元素
findFirst——返回第一個元素
findAny——返回當前流中的任意元素
count——返回流中元素的總個數(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);//檢查是否至少有一個元素小于2
//true

noneMatch

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

findFirst

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

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

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

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6);
long count = list.stream().count();//統(tǒng)計元素個數(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) ——可以將流中元素按照指定的二院運算反復結合起來吩蔑,得到一個值

    identity : 起始值

    BinaryOperator : 二元運算

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

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

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

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

  2. 收集(collect)

collect——將流轉換為其他形式。接收一個 Collector接口的實現(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ù)指定的元素對流中數(shù)據(jù)進行分組

//按照員工姓氏分組,這里不考慮復姓
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);
});

輸出結果為:

田:[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 : 按照給定條件對流中元素進行分區(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);
});

輸出結果為:

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)建一個 Optional 實例

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

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

isPresent() : 判斷是否包含值

orElse(T t) : 如果調用對象包含值,返回該值赘娄,否則返回t

orElseGet(Supplier s) :如果調用對象包含值遣臼,返回該值,否則返回 s 獲取的值

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

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

小結

Stream 是 Java8 中處理集合的關鍵抽象概念胖眷,它可以指定你希望對集合進行的操作珊搀,可以執(zhí)行非常復雜的查找、過濾和映射數(shù)據(jù)等操作劳淆。使用Stream API 對集合數(shù)據(jù)進行操作,就類似于使用 SQL 執(zhí)行的數(shù)據(jù)庫查詢曲掰。也可以使用 Stream API 來并行執(zhí)行操作。

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

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

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末翰守,一起剝皮案震驚了整個濱河市了袁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌崭庸,老刑警劉巖执赡,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡究履,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門蜜葱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人揭鳞,你說我怎么就攤上這事∨依妫” “怎么了焰轻?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蝠筑。 經(jīng)常有香客問我狞膘,道長,這世上最難降的妖魔是什么菱肖? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任稳强,我火速辦了婚禮渠缕,結果婚禮上燕差,老公的妹妹穿的比我還像新娘测暗。我一直安慰自己磨澡,他們只是感情好碗啄,可當我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著秩命,像睡著了一般尉共。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上弃锐,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天袄友,我揣著相機與錄音,去河邊找鬼霹菊。 笑死剧蚣,一個胖子當著我的面吹牛支竹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播鸠按,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼礼搁,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了目尖?” 一聲冷哼從身側響起馒吴,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎瑟曲,沒想到半個月后饮戳,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡洞拨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年扯罐,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片烦衣。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡歹河,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出花吟,到底是詐尸還是另有隱情秸歧,我是刑警寧澤,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布衅澈,位于F島的核電站寥茫,受9級特大地震影響,放射性物質發(fā)生泄漏矾麻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一芭梯、第九天 我趴在偏房一處隱蔽的房頂上張望险耀。 院中可真熱鬧,春花似錦玖喘、人聲如沸甩牺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽贬派。三九已至,卻和暖如春澎媒,著一層夾襖步出監(jiān)牢的瞬間搞乏,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工戒努, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留请敦,地道東北人。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像侍筛,于是被迫代替她去往敵國和親萤皂。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,779評論 2 354

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

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理匣椰,服務發(fā)現(xiàn)裆熙,斷路器,智...
    卡卡羅2017閱讀 134,654評論 18 139
  • 先看一段代碼 Stream API的歷史 在java8引入 受益于lambda表達式 lambda表達式 接口常被...
    ThomasYoungK閱讀 631評論 0 0
  • 1. Java基礎部分 基礎部分的順序:基本語法禽笑,類相關的語法入录,內(nèi)部類的語法,繼承相關的語法蒲每,異常的語法纷跛,線程的語...
    子非魚_t_閱讀 31,631評論 18 399
  • 一. Java基礎部分.................................................
    wy_sure閱讀 3,811評論 0 11
  • 一提起公務員這個職業(yè)贫奠,很多人的第一反應是:一張報紙一杯茶、混吃等死坐一天望蜡。嗯唤崭,一般有這反應的大約都是沒有真正進入過...
    一心一意做財女閱讀 1,633評論 2 5