Java基礎系列-Lambda

唯一浩哥發表於2020-10-22

原創文章,轉載請標註出處:https://www.cnblogs.com/V1haoge/p/10755338.html

一、概述

JDK1.8引入了函數語言程式設計,重點包括函式式介面、lambda表示式、方法引用等。

所謂函數語言程式設計就是將函式(一段操作)作為一個基本單位進行傳遞。以前的Java中引數只能是具體的變數,函數語言程式設計打破這一規範,可以將整個方法作為一個引數傳遞。

Java畢竟是物件導向的程式語言,你要傳遞的東西,必須是一個類或介面的物件或者一個基本型別變數,所以Java就定義了函式式介面,用來承載傳遞的函式。

二、函式式介面

2.1 函式式介面

函式式介面是在JDK1.8中提出的新概念,但對應的卻是老結構,在以往版本的JDK中就已經存在這種結構,只是沒有定義化。

函式式介面就是隻有一個抽象方法的介面。常用的函式式介面有Runnable、Comparator等。

JDK1.8將這些介面取了一個統一的名稱函式式介面,為了規範化,同時避免使用者自定義函式式介面時錯誤的新增了其他的抽象方法,而定義了一個註解:@FunctionalInterface,凡是由該註解標註的介面,統統為函式式介面,強制性的只有一個抽象方法。

為了函式式介面的擴充套件,JDK對介面規範進行了進一步修改,介面中除了可以定義抽象方法之外,還能夠定義靜態方法,和預設方法,而且這兩種方法可以擁有自己的實現。其中靜態方法一般作為工具方法,而預設方法是可以被繼承重寫的,還能擁有一個預設的實現。除此之外,函式式介面中還可以重寫Object中定義的方法。

// 典型函式式介面
@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
// 自定義函式式介面
@FunctionalInterface
public interface Itest {
    void test();
    boolean equals(Object obj);// 重寫Object中的方法
    default void defaultMethod(){
        // 這是一個預設方法
    }
    static void staticMethod(){
        // 這是一個靜態方法
    }
}

2.2 預定義的函式式介面

JDK 1.8為我們預定義了許多函式式介面,它們位於java.util.function包中。

序號 介面名 抽象方法 說明 備註
1 Supplier<T> T get() 無輸入引數,通過一系列操作產生一個結果返回 無中生有
2 IntSupplier int getAsInt() 通過操作返回一個int值 無中生有
3 LongSupplier long getAsLong() 通過操作返回一個long值 無中生有
4 DoubleSupplier double getAsDouble() 通過操作返回一個double值 無中生有
5 BooleanSupplier boolean getAsBoolean() 通過操作返回一個boolean值 無中生有
6 Consumer<T> void accept(T t) 一個輸入引數,針對引數做一系列操作,無返回值 消費掉了
7 IntConsumer void accept(int value) 一個int型輸入引數,針對引數做一系列操作,無返回值 消費掉了
8 LongConsumer void accept(long value) 一個long型輸入引數,針對引數做一系列操作,無返回值 消費掉了
9 DoubleConsumer void accept(double value) 一個double型輸入引數,針對引數做一系列操作,無返回值 消費掉了
10 BiConsumer<T, U> void accept(T t, U u) 兩個輸入引數,針對引數做一系列操作,無返回值 消費掉了
11 ObjIntConsumer<T> void accept(T t, int value) 兩個輸入引數,一個自定義型別T,另一個指定位int型,針對引數做一系列操作,無返回值 消費掉了
12 ObjLongConsumer<T> void accept(T t, long value) 兩個輸入引數,一個自定義型別T,另一個指定位long型,針對引數做一系列操作,無返回值 消費掉了
13 ObjDoubleConsumer<T> void accept(T t, double value) 兩個輸入引數,一個自定義型別T,另一個指定位double型,針對引數做一系列操作,無返回值 消費掉了
14 Function<T, R> R apply(T t) 一個引數,一個返回值,針對引數生成一個返回值 一因一果
15 IntFunction<R> R apply(int value) 一個int引數,一個自定義返回值,根據給定的int引數生成一個返回值 一因一果
16 LongFunction<R> R apply(long value) 一個long引數,一個自定義返回值,根據給定的long引數生成一個返回值 一因一果
17 DoubleFunction<R> R apply(double value) 一個double引數,一個自定義返回值,根據給定的double引數生成一個返回值 一因一果
18 ToIntFunction<T> int applyAsInt(T value) 一個引數,針對引數生成一個int返回值 一因一果
19 ToLongFunction<T> long applyAsLong(T value) 一個引數,針對引數生成一個long返回值 一因一果
20 ToDoubleFunction<T> double applyAsDouble(T value) 一個引數,針對引數生成一個double返回值 一因一果
21 BiFunction<T, U, R> R apply(T t, U u) 兩個輸入引數,一個返回值,根據引數生成一個返回值 多因一果
22 IntToDoubleFunction double applyAsDouble(int value) 一個int引數,根據引數生成一個double結果返回 一因一果
23 IntToLongFunction long applyAsLong(int value) 一個int引數,根據引數生成一個long結果返回 一因一果
24 LongToDoubleFunction double applyAsDouble(long value) 一個long引數,根據引數生成一個double結果返回 一因一果
25 LongToIntFunction int applyAsInt(long value) 一個long引數,根據引數生成一個int果返回 一因一果
26 DoubleToIntFunction int applyAsInt(double value) 一個double引數,根據引數生成一個int結果返回 一因一果
27 DoubleToLongFunction long applyAsLong(double value) 一個double引數,根據引數生成一個long結果返回 一因一果
28 ToIntBiFunction<T, U> int applyAsInt(T t, U u) 兩個輸入引數,根據引數生成一個int返回值 多因一果
29 ToLongBiFunction<T, U> long applyAsLong(T t, U u) 兩個輸入引數,根據引數生成一個long返回值 多因一果
30 ToDoubleBiFunction<T, U> double applyAsDouble(T t, U u) 兩個輸入引數,根據引數生成一個double返回值 多因一果
31 Predicate<T> boolean test(T t) 一個引數,返回校驗boolean結果 校驗引數
32 BiPredicate<T, U> boolean test(T t, U u) 兩個引數,返回校驗boolean結果 校驗引數
33 IntPredicate boolean test(int value) 一個int引數,返回校驗boolean結果 校驗引數
34 LongPredicate boolean test(long value) 一個long引數,返回校驗boolean結果 校驗引數
35 DoublePredicate boolean test(double value) 一個double引數,返回校驗boolean結果 校驗引數
36 UnaryOperator<T> T apply(T t) 一個T型引數,通過操作返回一個T型結果 一元操作
37 IntUnaryOperator int applyAsInt(int operand) 一個int引數,通過操作返回一個int結果 一元操作
38 LongUnaryOperator long applyAsLong(long operand) 一個long引數,通過操作返回一個long結果 一元操作
39 DoubleUnaryOperator double applyAsDouble(double operand) 一個double引數,通過操作返回一個double結果 一元操作
40 BinaryOperator<T> T apply(T t1, T t2) 兩個T型引數,通過操作返回一個T型結果 二元操作
41 IntBinaryOperator int applyAsInt(int left, int right) 兩個int引數,通過操作返回一個int結果 二元操作
42 LongBinaryOperator long applyAsLong(long left, long right) 兩個long引數,通過操作返回一個long結果 二元操作
43 DoubleBinaryOperator double applyAsDouble(double left, double right) 兩個double引數,通過操作返回一個double結果 二元操作

三、Lambda表示式

Lambda表示式,簡化了匿名內部類的操作方式。

Lamnda表示式可以用在兩個地方,一種是集合遍歷,另一種就是替換匿名內部類。

前者基於Iterable介面和Map介面中定義的forEach方法,後者則依據函式式介面。

3.1 forEach方法

其實forEach方法是對函式式介面的有效利用,將遍歷的書寫流程簡化,我們不用再寫一大堆的for迴圈框架程式碼。

public class LanbdaTest {
    public static void main(String[] args) {
        List<String> list = Collections.EMPTY_LIST;
        list.forEach(System.out::println);
        Map<String,Object> map = Collections.EMPTY_MAP;
        map.forEach((k,v) -> System.out.println(k + ":"+ v));
    }
}

forEach方法的引數是Consumer或者BiConsumer,主要用於消費資源,即需要提供引數,但是沒有返回值的方法(函式或操作)。

forEach方法最開始是在Iterable介面和Map介面中定義的,這是以預設方法的方式定義的,分別以Consumer和BiConsumer作為入參。

Iterable中的forEach方法:

public interface Iterable<T> {
    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }
}

Iterable的實現類均可以通過重寫該方法來自定義遍歷的方式。

比如以陣列為底層結構的ArrayList、CopyOnWriteArrayList、CopyOnWriteArraySet等都是以普通for迴圈來實現的遍歷。而以連結串列為底層結構的LinkedList則沒有重寫forEach方法,採用預設方法中簡化的for迴圈,編譯器會對其進行處理,將其採用Iterator進行遍歷。

Map中的forEach方法:

public interface Map<K,V> {
    default void forEach(BiConsumer<? super K, ? super V> action) {
        Objects.requireNonNull(action);
        for (Map.Entry<K, V> entry : entrySet()) {
            K k;
            V v;
            try {
                k = entry.getKey();
                v = entry.getValue();
            } catch(IllegalStateException ise) {
                // this usually means the entry is no longer in the map.
                throw new ConcurrentModificationException(ise);
            }
            action.accept(k, v);
        }
    } 
}

在常用的HashMap和TreeMap中都對該方法進行了重寫,HashMap採用陣列+連結串列(紅黑樹)的方式實現,但是遍歷的時候採用的就是陣列+連結串列雙重遍歷,因為在HashMap中的紅黑樹同時還是一個雙向連結串列。而TreeMap中則是使用樹結構的中序遍歷方式實現的。

3.2 替換匿名內部類

Lambda替換匿名內部類有一個前提,那就是這個匿名內部類的介面型別必須為函式式介面,如果不是函式式介面,是無法使用Lambda替換的。

常用的函式式介面為Runnable,使用匿名內部類方式如下:

public class LambdaTest {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("採用匿名內部類");
            }
        });
    }
}

使用Lambda替換如下:

public class LambdaTest {
    public static void main(String[] args) {
        Thread t1 = new Thread(()->System.out.println("採用Lambda方式"));
    }
}

Lambda表示式最大的作用其實就是替換匿名內部類,簡化這種寫法。

四、方法引用

方法引用出現的目的是為了解決所需的操作已經存在的情況。

當我們需要傳遞的操作已經存在,那就不必再費盡心思的再寫一個出來啦,直接使用方法引用來將已有的方法給它就行了。

方法引用使用“::”雙英文冒號組成的操作符來指定方法。

使用方法引用之後,你會很不適應,因為引數哪去啦???

是的,引數不再是顯式傳遞,採用方法引用之後,引數會自動傳遞,我們舉個例子看看簡單的原理:

public class LanbdaTest {
    public static String getName(Supplier<String> supplier){
        return supplier.get();
    }
    public static void main(String[] args) {
        Person person = new Person("huahua");
        System.out.println(getName(person::getName));
    }
}
class Person{
    private String name;
    public Person(String name){
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

執行結果為:

huahua

解析:
首先我們使用了方法引用:person::getName,Person類中有已定義好的獲取name的方法,這裡就可以直接引用該方法。Supplier是供應者,可以無中生有,也就是不需要引數,產生一個返回值。

Person中的getName方法,明顯就符合Supplier的格式,沒有引數,但是返回了一個結果,所以這裡就可以直接傳遞person::getName。

方法引用的種類:

  • 類的構造器引用:ArrayList::new、String[]::new
  • 類的靜態方法引用:String::valueOf、Integer::valueOf
  • 類的例項方法引用:String::length、Person::getName
  • 物件的例項方法引用:sring::length、person::getName

方法引用於Lambda可以算是平等,並列的關係,Lambda用於自定義操作,方法引用用於引用已存在的操作。

相關文章