天天看點

Java Lambda表達式與Stream API小結1 Lambda 表達式(Lambda Expressions)2 函數式(Functional)接口3 方法引用與構造器引用4 Stream API5 Optional 類

1 Lambda 表達式(Lambda Expressions)

Lambda 是一個匿名函數,我們可以把 Lambda 表達式了解為是一段可以傳遞的代碼(将代碼像資料一樣進行傳遞)。使用它可以寫出更簡潔、更靈活的代碼。作為一種更緊湊的代碼風格,使Java的語言表達能力得到了提升。

// 匿名内部類
Runnable r = new Runnable(){
    @Override
    public void run(){
        System.out.println("Hello World!");
    }
}

// Lambda表達式
Runnable r = () -> System.out.println("Hello World!");
           

看着是不是有點牛皮~?再來一個:

//原來使用匿名内部類作為參數傳遞
TreeSet<String> ts = new TreeSet<>(new Comparator<String>(){
    @Override
    public int compare(String o1,String o2){
        return Integer.compare(o1.length(),o2.length());
    }
});
// Lambda 表達式
TreeSet<String> ts = new TreeSet<>(
    (o1,o2) -> Integer.compare(o1.length(),o2.length());
);
           

1.1 Lambda表達式文法

Lambda 表達式:在Java 8 語言中引入的一種新的文法元素和操作符。這個操作符為 “->” , 該操作符被稱為 Lambda 操作符或箭頭操作符。它将 Lambda 分為兩個部分:

左側:指定了 Lambda 表達式需要的參數清單

右側:指定了 Lambda 體,是抽象方法的實作邏輯,也即 Lambda 表達式要執行的功能。

Java Lambda表達式與Stream API小結1 Lambda 表達式(Lambda Expressions)2 函數式(Functional)接口3 方法引用與構造器引用4 Stream API5 Optional 類
Java Lambda表達式與Stream API小結1 Lambda 表達式(Lambda Expressions)2 函數式(Functional)接口3 方法引用與構造器引用4 Stream API5 Optional 類

上述 Lambda 表達式中的參數類型都是由編譯器推斷得出的。Lambda 表達式中無需指定類型,程式依然可以編譯,這是因為 javac 根據程式的上下文,在背景推斷出了參數的類型。Lambda 表達式的類型依賴于上下文環境,是由編譯器推斷出來的。這就是所謂的“類型推斷”。

Java Lambda表達式與Stream API小結1 Lambda 表達式(Lambda Expressions)2 函數式(Functional)接口3 方法引用與構造器引用4 Stream API5 Optional 類

2 函數式(Functional)接口

隻包含一個抽象方法的接口,稱為函數式接口。

你可以通過 Lambda 表達式來建立該接口的對象。(若 Lambda 表達式抛出一個受檢異常(即:非運作時異常),那麼該異常需要在目标接口的抽象方法上進行聲明)。

我們可以在一個接口上使用 @FunctionalInterface 注解,這樣做可以檢查它是否是一個函數式接口。同時 javadoc 也會包含一條聲明,說明這個接口是一個函數式接口。

在java.util.function包下定義了java 8 的豐富的函數式接口。如下代碼便是Runnable接口的代碼,有@FunctionalInterface注解 

@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}
           

Java從誕生日起就是一直倡導“一切皆對象”,在java裡面面向對象(OOP)程式設計是一切。但是随着python、scala等語言的興起和新技術的挑戰,java不得不做出調整以便支援更加廣泛的技術要求,也即java不但可以支援OOP還可以支援OOF(面向函數程式設計)

在函數式程式設計語言當中,函數被當做一等公民對待。在将函數作為一等公民的程式設計語言中,Lambda表達式的類型是函數。但是在Java8中,有所不同。在Java8中,Lambda表達式是對象,而不是函數,它們必須依附于一類特别的對象類型——函數式接口。

簡單的說,在Java8中,Lambda表達式就是一個函數式接口的執行個體。這就是Lambda表達式和函數式接口的關系。也就是說,隻要一個對象是函數式接口的執行個體,那麼該對象就可以用Lambda表達式來表示。

是以以前用匿名内部類表示的現在都可以用Lambda表達式來寫。

@FunctionalInterface
public interface MyInterface1{
   
    public int get();
}

@FunctionalInterface
public interface MyInterface2<T>{
   
    public T get(T t);
}
           

Java 内置四大核心函數式接口

函數式接口 參數類型 傳回類型 用途

Consumer<T>

消費型接口

T void

對類型為T的對象應用操作,包含方法:

void accept(T t)

Supplier<T>

供給型接口

T 傳回類型為T的對象,包含方法:T get()

Function<T, R>

函數型接口

T R 對類型為T的對象應用操作,并傳回結果。結果是R類型的對象。包含方法:R apply(T t)

Predicate<T>

斷定型接口

T boolean

确定類型為T的對象是否滿足某限制,并傳回 boolean 值。包含方法

boolean test(T t)

其他接口

函數式接口 參數類型 傳回類型 用途
BiFunction<T, U, R> T, U R

對類型為 T, U 參數應用操作,傳回 R 類型的結果。包含方法為 

R apply(T t, U u);

UnaryOperator<T>

(Function子接口)

T T

對類型為T的對象進行一進制運算,并傳回T類型的結果。包含方法為

T apply(T t);

BinaryOperator<T>

(BiFunction 子接口)

T, T T

對類型為T的對象進行二進制運算,并傳回T類型的結果。包含方法為 

T apply(T t1, T t2);

BiConsumer<T, U> T, U void 對類型為T, U 參數應用操作。包含方法為  void accept(T t, U u)
BiPredicate<T,U> T,U boolean 包含方法為  boolean test(T t,U u)

ToIntFunction<T>

ToLongFunction<T>

ToDoubleFunction<T>

T

int

long

double

分别計算int、long、double、值的函數

IntFunction<R>

LongFunction<R>

DoubleFunction<R>

int

long

double

R 參數分别為int、long、double 類型的函數

3 方法引用與構造器引用

3.1 方法引用(Method References)

當要傳遞給Lambda體的操作,已經有實作的方法了,可以使用方法引用!

方法引用就是Lambda表達式,就是函數式接口的一個執行個體,通過方法的名字來指向一個方法,可以認為是Lambda表達式的一個文法糖。

要求:實作抽象方法的參數清單和傳回值類型,必須與方法引用的方法的參數清單和傳回值類型保持一緻!

方法引用:使用操作符 “::” 将類(或對象) 與 方法名分隔開來。

如下三種主要使用情況:

對象::執行個體方法名

類::靜态方法名

類::執行個體方法名

Java Lambda表達式與Stream API小結1 Lambda 表達式(Lambda Expressions)2 函數式(Functional)接口3 方法引用與構造器引用4 Stream API5 Optional 類
Java Lambda表達式與Stream API小結1 Lambda 表達式(Lambda Expressions)2 函數式(Functional)接口3 方法引用與構造器引用4 Stream API5 Optional 類

注意:當函數式接口方法的第一個參數是需要引用方法的調用者,并且第二個參數是需要引用方法的參數(或無參數)時:ClassName::methodName。

3.2 構造器引用

格式:   ClassName::new

與函數式接口相結合,自動與函數式接口中方法相容。

可以把構造器引用指派給定義的方法,要求構造器參數清單要與接口中抽象方法的參數清單一緻!且方法的傳回值即為構造器對應類的對象。

Java Lambda表達式與Stream API小結1 Lambda 表達式(Lambda Expressions)2 函數式(Functional)接口3 方法引用與構造器引用4 Stream API5 Optional 類

3.3 數組引用

格式: type[] :: new

Java Lambda表達式與Stream API小結1 Lambda 表達式(Lambda Expressions)2 函數式(Functional)接口3 方法引用與構造器引用4 Stream API5 Optional 類

4 Stream API

Stream API ( java.util.stream) 把真正的函數式程式設計風格引入到Java中。這是目前為止對Java類庫最好的補充,因為Stream API可以極大提供Java程式員的生産力,讓程式員寫出高效率、幹淨、簡潔的代碼。

Stream 是 Java8 中處理集合的關鍵抽象概念,它可以指定你希望對集合進行的操作,可以執行非常複雜的查找、過濾和映射資料等操作。 使用Stream API 對集合資料進行操作,就類似于使用 SQL 執行的資料庫查詢。也可以使用 Stream API 來并行執行操作。簡言之,Stream API 提供了一種高效且易于使用的處理資料的方式。

Stream是資料管道,用于操作資料源(集合、數組等)所生成的元素序列。“集合講的是資料,Stream講的是計算!”。其特點如下:

1)Stream 不是集合,自己不會存儲元素。

2)Stream 不會改變源對象。相反,他們會傳回一個持有結果的新Stream。

3)Stream 操作是延遲執行的。必須搞清楚有哪些資料才能往下執行,這意味着他們會等到需要結果的時候才執行。

4)Stream隻能“消費”一次,如果想繼續做其他操作,需要重新擷取stream對象

5)更像一個進階的iterator,單向,不可往複,資料隻能周遊一次,周遊過一次後即用盡了,但是可以并行化資料!

4.1 Stream 的操作三個步驟

1- 建立 Stream

一個資料源(如:集合、數組),擷取一個流

2- 中間操作

一個中間操作鍊,對資料源的資料進行處理

3- 終止操作(終端操作)

一旦執行終止操作,就執行中間操作鍊,并産生結果。之後,不會再被使用

Java Lambda表達式與Stream API小結1 Lambda 表達式(Lambda Expressions)2 函數式(Functional)接口3 方法引用與構造器引用4 Stream API5 Optional 類
Java Lambda表達式與Stream API小結1 Lambda 表達式(Lambda Expressions)2 函數式(Functional)接口3 方法引用與構造器引用4 Stream API5 Optional 類

4.2 建立Stream 的方式

1) 通過集合

Java8 中的 Collection 接口被擴充,提供了兩個擷取流的方法:

default Stream<E> stream() : 傳回一個順序流

default Stream<E> parallelStream() : 傳回一個并行流

2) 通過數組

Java8 中的 Arrays 的靜态方法 stream() 可以擷取數組流:

static <T> Stream<T> stream(T[] array): 傳回一個流

重載形式,能夠處理對應基本類型的數組:

public static IntStream stream(int[] array)

public static LongStream stream(long[] array)

public static DoubleStream stream(double[] array)

3)通過Stream的of()

可以調用Stream類靜态方法 of(), 通過顯示值建立一個流。它可以接收任意數量的參數。

public static<T> Stream<T> of(T... values) : 傳回一個流

4)建立無限流

可以使用靜态方法 Stream.iterate() 和 Stream.generate(), 建立無限流。

疊代

public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)

生成

public static<T> Stream<T> generate(Supplier<T> s)

4.3 Stream 的中間操作

多個中間操作可以連接配接起來形成一個流水線,除非流水線上觸發終止操作,否則中間操作不會執行任何的處理!而在終止操作時一次性全部處理,稱為“惰性求值”。

1-篩選與切片

方  法 描  述
filter(Predicate p) 接收 Lambda , 從流中排除某些元素
distinct() 篩選,通過流所生成元素的 hashCode() 和 equals() 去除重複元素
limit(long maxSize) 截斷流,使其元素不超過給定數量
skip(long n) 跳過元素,傳回一個扔掉了前 n 個元素的流。若流中元素不足 n 個,則傳回一個空流。與 limit(n) 互補

filter(Predicate p)——接收 Lambda , 從流中排除某些元素。

limit(n)——截斷流,使其元素不超過給定數量。

skip(n) —— 跳過元素,傳回一個扔掉了前 n 個元素的流。若流中元素不足 n 個,則傳回一個空流。與 limit(n) 互補

distinct()——篩選,通過流所生成元素的 hashCode() 和 equals() 去除重複元素

2-映 射

方法 描述
map(Function f) 接收一個函數作為參數,該函數會被應用到每個元素上,并将其映射成一個新的元素。
mapToDouble(ToDoubleFunction f) 接收一個函數作為參數,該函數會被應用到每個元素上,産生一個新的 DoubleStream。
mapToInt(ToIntFunction f) 接收一個函數作為參數,該函數會被應用到每個元素上,産生一個新的 IntStream。
mapToLong(ToLongFunction f) 接收一個函數作為參數,該函數會被應用到每個元素上,産生一個新的 LongStream。
flatMap(Function f) 接收一個函數作為參數,将流中的每個值都換成另一個流,然後把所有流連接配接成一個流

map(Function f)——接收一個函數作為參數,将元素轉換成其他形式或提取資訊,該函數會被應用到每個元素上,并将其映射成一個新的元素。

flatMap(Function f)——接收一個函數作為參數,将流中的每個值都換成另一個流,然後把所有流連接配接成一個流。

3-排序

方法 描述
sorted() 産生一個新流,其中按自然順序排序
sorted(Comparator com) 産生一個新流,其中按比較器順序排序

sorted()——自然排序

sorted(Comparator com)——定制排序

4.4 Stream 的終止操作

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

•流進行了終止操作後,不能再次使用。

1-比對與查找

方法 描述
allMatch(Predicate p) 檢查是否比對所有元素
anyMatch(Predicate p) 檢查是否至少比對一個元素
noneMatch(Predicate  p) 檢查是否沒有比對所有元素
findFirst() 傳回第一個元素
findAny() 傳回目前流中的任意元素

allMatch(Predicate p)——檢查是否比對所有元素

anyMatch(Predicate p)——檢查是否至少比對一個元素

noneMatch(Predicate p)——檢查是否沒有比對的元素

findFirst——傳回第一個元素

findAny——傳回目前流中的任意元素

count——傳回流中元素的總個數

max(Comparator c)——傳回流中最大值

min(Comparator c)——傳回流中最小值

forEach(Consumer c)——内部疊代

方法 描述
count() 傳回流中元素總數
max(Comparator c) 傳回流中最大值
min(Comparator c) 傳回流中最小值
forEach(Consumer c) 内部疊代(使用 Collection 接口需要使用者去做疊代,稱為外部疊代。相反,Stream API 使用内部疊代——它幫你把疊代做了)

2-歸約

方法 描述
reduce(T iden, BinaryOperator b) 可以将流中元素反複結合起來,得到一個值。傳回 T
reduce(BinaryOperator b) 可以将流中元素反複結合起來,得到一個值。傳回 Optional<T>

reduce(T identity, BinaryOperator)——可以将流中元素反複結合起來,得到一個值。傳回 T 

reduce(BinaryOperator) ——可以将流中元素反複結合起來,得到一個值。傳回 Optional<T>

3-收集

方  法 描  述
collect(Collector c) 将流轉換為其他形式。接收一個 Collector接口的實作,用于給Stream中元素做彙總的方法

collect(Collector c)——将流轉換為其他形式。接收一個 Collector接口的實作,用于給Stream中元素做彙總的方法

方法 傳回類型 作用
toList List<T> 把流中元素收集到List
List<Employee> emps= list.stream().collect(Collectors.toList());
toSet Set<T> 把流中元素收集到Set
Set<Employee> emps= list.stream().collect(Collectors.toSet());
toCollection Collection<T> 把流中元素收集到建立的集合
Collection<Employee> emps =list.stream().collect(Collectors.toCollection(ArrayList::new));
counting Long 計算流中元素的個數
long count = list.stream().collect(Collectors.counting());
summingInt Integer 對流中元素的整數屬性求和
int total=list.stream().collect(Collectors.summingInt(Employee::getSalary));
averagingInt Double 計算流中元素Integer屬性的平均值
double avg = list.stream().collect(Collectors.averagingInt(Employee::getSalary));
summarizingInt IntSummaryStatistics 收集流中Integer屬性的統計值。如:平均值
int SummaryStatisticsiss= list.stream().collect(Collectors.summarizingInt(Employee::getSalary));
joining String 連接配接流中每個字元串
String str= list.stream().map(Employee::getName).collect(Collectors.joining());
maxBy Optional<T> 根據比較器選擇最大值
Optional<Emp>max= list.stream().collect(Collectors.maxBy(comparingInt(Employee::getSalary)));
minBy Optional<T> 根據比較器選擇最小值
Optional<Emp> min = list.stream().collect(Collectors.minBy(comparingInt(Employee::getSalary)));
reducing 歸約産生的類型 從一個作為累加器的初始值開始,利用BinaryOperator與流中元素逐個結合,進而歸約成單個值
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<K, List<T>> 根據某屬性值對流分組,屬性為K,結果為V
Map<Emp.Status, List<Emp>> map= list.stream().collect(Collectors.groupingBy(Employee::getStatus));
partitioningBy Map<Boolean, List<T>> 根據true或false進行分區
Map<Boolean,List<Emp>> vd = list.stream().collect(Collectors.partitioningBy(Employee::getManage));

并行流與串行流

并行流就是把一個内容分成多個資料塊,并用不同的線程分别處理每個資料塊的流。

Java 8 中将并行進行了優化,我們可以很容易的對資料進行并行操作。Stream API 可以聲明性地通過 parallel() 與 sequential() 在并行流與順序流之間進行切換。

5 Optional 類

到目前為止,臭名昭著的空指針異常是導緻Java應用程式失敗的最常見原因。以前,為了解決空指針異常,Google公司著名的Guava項目引入了Optional類,Guava通過使用檢查空值的方式來防止代碼污染,它鼓勵程式員寫更幹淨的代碼。受到Google Guava的啟發,Optional類已經成為Java 8類庫的一部分。

Optional實際上是個容器:它可以儲存類型T的值,或者僅僅儲存null。Optional提供很多有用的方法,這樣我們就不用顯式進行空值檢測。

Optional類的Javadoc描述如下:這是一個可以為null的容器對象。如果值存在則isPresent()方法會傳回true,調用get()方法會傳回該對象。

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

常用方法:

Optional.empty() : 建立一個空的 Optional 執行個體

Optional.of(T t) : 建立一個 Optional 執行個體

Optional.ofNullable(T t):若 t 不為 null,建立 Optional 執行個體,否則建立空執行個體

isPresent() : 判斷是否包含值

T get(): 如果調用對象包含值,傳回該值,否則抛異常

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

orElseGet(Supplier s) :如果調用對象包含值,傳回該值,否則傳回 s 擷取的值

map(Function f): 如果有值對其處理,并傳回處理後的Optional,否則傳回 Optional.empty()

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

Optional<Employee> op = Optional.of(new Employee(101, "張三", 18, 9999.99));

Optional<String> op2 = op.map(Employee::getName);

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

Optional<String> op3 = op.flatMap((e) -> Optional.of(e.getName()));

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