十分鐘學會Java8的lambda表達式和Stream API

集成開發環境 文章 IT編程技能提升 2019-05-04
十分鐘學會Java8的lambda表達式和Stream API

01:前言

一直在用JDK8 ,卻從未用過Stream,為了對數組或集合進行一些排序、過濾或數據處理,只會寫for循環或者foreach,這就是我曾經的一個寫照。

剛開始寫寫是打基礎,但寫的多了,各種乏味,非過來人不能感同身受。今天,我就要分享一篇如何解決上述問題的新方法 - Stream API。但學習Stream之前卻不得不學一下Lambda表達式。說實話,網上介紹Lambda表達式的文章很多,大多晦澀難懂,今天我就想用自己的理解去說一下Lambda表達式是如何讓我們的代碼寫的更少!

02:來自IDEA的提示

在IDE中,你是否遇到在寫以下列代碼時,被友情提示的情況:

new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread");
}
});

這時候,我們按一下快捷鍵,IDE自動幫我們把代碼優化為醬個樣子:

new Thread(() -> System.out.println("thread"));

這就是Java8的新特性:lambda表達式。

03:Lambda入門

借用上面的示例,在調用new Thread的含參構造方法時,我們通過匿名內部類的方式實現了Runnable對象,但其實有用的代碼只有System.out.println("thread")這一句,而我們卻要為了這一句去寫這麼多行代碼。正是這個問題,才有了Java8中的lambda表達式。那lambd表達式究竟是如何簡化代碼的呢?

先來看lambda表達式的語法:

() -> {}

(): 括號就是接口方法的括號,接口方法如果有參數,也需要寫參數。只有一個參數時,括號可以省略。

->: 分割左右部分的,沒啥好說的。

{} : 要實現的方法體。只有一行代碼時,可以不加括號,可以不寫return。

看了上面的解釋,也就不難理解IDE優化後的代碼了。不過看到這裡你也許意識到,如果接口中有多個方法時,按照上面的邏輯lambda表達式恐怕不行了。沒錯,lambda表達式只適用於函數型接口。說白了,函數型接口就是隻有一個抽象方法的接口。這種類型的接口還有一個對應的註解:@FunctionalInterface。為了讓我們在需要這種接口時不再自己去創建,Java8中內置了四大核心函數型接口。

消費型接口(有參無返回值)

Consumer<T>
void accept(T t);

供給型接口(無參有返回值)

Supplier<T>
T get();

函數型接口(有參有返回值)

Function<T, R>
R apply(T t);

斷言型接口(有參有布爾返回值)

Predicate<T>
boolean test(T t);


看到這裡如果遇到一般的lambda表達式,你應該可以從容面對了,但高級點的恐怕看到還是懵,不要急,其實也不難。Lambda表達式還有兩種簡化代碼的手段,分別是方法引用構造引用


04:方法引用

方法引用是什麼呢?如果我們要實現接口的方法與另一個方法A類似,(這裡的類似是指參數類型與返回值部分相同),我們直接聲明A方法即可。也就是,不再使用lambda表達式的標準形式,改用高級形式。無論是標準形式還是高級形式,都是lambda表達式的一種表現形式。

舉例:

Function function1 = (x) -> x;
Function function2 = String::valueOf;

對比Function接口的抽象方法與String的value方法,可以看到它們是類似的。

R apply(T t);

public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}

方法引用的語法:

對象::實例方法
類::靜態方法
類::實例方法

前兩個很容易理解,相當於對象調用實例方法,類調用靜態方法一樣。只是第三個需要特殊說明。

當出現如下這種情況時:

Compare<Boolean> c = (a, b) -> a.equals(b);

用lambda表達式實現Compare接口的抽象方法,並且方法體只有一行,且該行代碼為參數1調用方法傳入參數2。此時,就可以簡化為下面這種形式:

Compare<Boolean> c = String::equals;

也就是“類::實例方法”的形式。

值得一提的是,當參數b不存在時,該方式依舊適用。例如:

Function function1 = (x) -> x.toString();
Function function1 = Object::toString;


05:構造引用

先來創建一個供給型接口對象:

Supplier<String> supplier = () -> new String();

在這個lammbda表達式中只做了一件事,就是返回一個新的Test對象,而這種形式可以更簡化:

Supplier<String> supplier = String::new;

提煉一下構造引用的語法:

類名::new

當通過含參構造方法創建對象,並且參數列表與抽象方法的參數列表一致,也就是下面的這種形式:

Function1 function = (x) -> new String(x);

也可以簡化為:

Function1 function = String::new;

特殊點的數組類型:

Function<Integer,String[]> function = (x) -> new String[x];

可以簡化為:

Function<Integer,String[]> function = String[]::new;


06:Lambda總結

上面並沒有給出太多的lambda實例,只是側重講了如何去理解lambda表達式。到這裡,不要懵。要記住lambda的本質:為函數型接口的匿名實現進行簡化與更簡化。

所謂的簡化就是lambda的標準形式,所謂的更簡化是在標準形式的基礎上進行方法引用和構造引用。

方法引用是拿已有的方法去實現此刻的接口。

構造引用是對方法體只有一句new Object()的進一步簡化。


07:Stream理解

如何理解Stream?在我看來,Stream 不是集合元素,它不是數據結構並不保存數據,它是有關算法和計算的,它更像一個高級版本的 Iterator。簡單來說,它的作用就是通過一系列操作將數據源(集合、數組)轉化為想要的結果。

Stream有三點非常重要的特性:

  1. Stream 是不會存儲元素的。
  2. Stream 不會改變原對象,相反,他們會返回一個持有結果的新Stream。
  3. Stream 操作是延遲執行的。意味著它們會等到需要結果的時候才執行。


08:Stream生成

Collection系的 stream() 和 parallelStream()

List<String> list = new ArrayList<>();
Stream<String> stream = list.stream();
Stream<String> stringStream = list.parallelStream();

通過Arrays.stram()

Stream<String> stream1 = Arrays.stream(new String[10]);

通過Stream.of()

Stream<Integer> stream2 = Stream.of(1, 2, 3);

通過Stream.iterate()生成無限流

Stream<Integer> iterate = Stream.iterate(0, (x) -> x + 2);
iterate.limit(10).forEach(System.out::println);

通過Stream.generate()

Stream<Double> generate = Stream.generate(() -> Math.random());generate.forEach(System.out::println);


09:Stream中間操作

多箇中間操作連接而成為流水線,流水線不遇到終止操作是不觸發任何處理的,所為又稱為“惰性求值”。

list.stream()
.map(s -> s + 1) //映射
.flatMap(s -> Stream.of(s)) //和map差不多,但返回類型為Stream,類似list.add()和list.addAll()的區別
.filter(s -> s < 1000) //過濾
.limit(5) //限制
.skip(1) //跳過
.distinct() //去重
.sorted() //自然排序
.sorted(Integer::compareTo) //自定義排序

關於map方法,參數為一個Function函數型接口的對象,也就是傳入一個參數返回一個對象。這個參數就是集合中的每一項。類似Iterator遍歷。其它的幾個操作思想都差不多。

執行上面的方法沒什麼用,因為缺少終止操作。


10:Stream的終止操作

常用的終止API如下:

list.stream().allMatch((x) -> x == 555); // 檢查是否匹配所有元素
list.stream().anyMatch(((x) -> x>600)); // 檢查是否至少匹配一個元素
list.stream().noneMatch((x) -> x>500); //檢查是否沒有匹配所有元素
list.stream().findFirst(); // 返回第一個元素
list.stream().findAny(); // 返回當前流中的任意一個元素
list.stream().count(); // 返回流中元素的總個數
list.stream().forEach(System.out::println); //內部迭代
list.stream().max(Integer::compareTo); // 返回流中最大值
Optional<Integer> min = list.stream().min(Integer::compareTo);//返回流中最小值
System.out.println("min "+min.get());

reduce (歸約):將流中元素反覆結合起來得到一個值

Integer reduce = list.stream()
.map(s -> (s + 1))
.reduce(0, (x, y) -> x + y); //歸約:0為第一個參數x的默認值,x是計算後的返回值,y為每一項的值。
System.out.println(reduce);
Optional<Integer> reduce1 = list.stream()
.map(s -> (s + 1))
.reduce((x, y) -> x + y); // x是計算後的返回值,默認為第一項的值,y為其後每一項的值。
System.out.println(reduce);

collect(收集):將流轉換為其他形式。需要Collectors類的一些方法。

//轉集合
Set<Integer> collect = list.stream()
.collect(Collectors.toSet());
List<Integer> collect2 = list.stream()
.collect(Collectors.toList());
HashSet<Integer> collect1 = list.stream()
.collect(Collectors.toCollection(HashSet::new));
//分組 {group=[444, 555, 666, 777, 555]}
Map<String, List<Integer>> collect3 = list.stream()
.collect(Collectors.groupingBy((x) -> "group"));//將返回值相同的進行分組
System.out.println(collect3);
//多級分組 {group={777=[777], 666=[666], 555=[555, 555], 444=[444]}}
Map<String, Map<Integer, List<Integer>>> collect4 = list.stream()
.collect(Collectors.groupingBy((x) -> "group", Collectors.groupingBy((x) -> x)));
System.out.println(collect4);
//分區 {false=[444], true=[555, 666, 777, 555]}
Map<Boolean, List<Integer>> collect5 = list.stream()
.collect(Collectors.partitioningBy((x) -> x > 500));
System.out.println(collect5);
//彙總
DoubleSummaryStatistics collect6 = list.stream()
.collect(Collectors.summarizingDouble((x) -> x));
System.out.println(collect6.getMax());
System.out.println(collect6.getCount());
//拼接 444,555,666,777,555
String collect7 = list.stream()
.map(s -> s.toString())
.collect(Collectors.joining(","));
System.out.println(collect7);
//最大值
Optional<Integer> integer = list.stream()
.collect(Collectors.maxBy(Integer::compare));
System.out.println(integer.get());

至於Stream的其它用法推薦參考下源碼與API文檔。

“我們相信人人都可以成為一個java開發大神,現在開始,找個師兄,帶你入門,學習的路上不再迷茫。這裡是java開發修真院,初學者轉行到互聯網行業的聚集地。"

“我自己是一名從事了多年開發的java老程序員,辭職目前在做自己的java私人定製課程,今年年初我花了一個月整理了一份最適合2019年學習的java學習乾貨,從最基礎的javase到spring各種框架都有整理,送給每一位java小夥伴,想要獲取的可以關注我的頭條號並在後臺私信我:java,即可免費獲取


原文地址:https://mp.weixin.qq.com/s/5UUD7N4Uxspnzcine_2AnA

作者:像風一樣 程序員柯南 如有侵權,請聯繫刪除,謝謝。

相關推薦

推薦中...