java8 Lambda表達式&函數(shù)接口&方法引用

Lambda表達式

java 8 的新特性,是一種語法糖,簡化了只有一個方法的匿名內(nèi)部類的編寫,讓編碼更方便,代碼更簡潔。

只有一個方法的接口(又叫函數(shù)式接口)在寫匿名內(nèi)部類時看起來很啰嗦,真正有用的只是方法實現(xiàn)中的那一段代碼。通常情況下我們使用匿名內(nèi)部類是想將功能作為參數(shù)傳遞給另外的方法,例如點擊按鈕時應(yīng)該采取的措施。Lambda表達式使我們能夠執(zhí)行此操作,將功能視為方法參數(shù),或?qū)⒋a視為數(shù)據(jù)。

個人理解:主要作用是為了更方便的創(chuàng)建 函數(shù)接口 的匿名內(nèi)部類的實例,減少啰嗦的代碼。

基本語法

主要是加了箭頭符號 '->' 稱為Lambda操作符或箭頭操作符

(參數(shù)) -> {函數(shù)體}

左邊是參數(shù),中間是箭頭符號,右邊是方法體

這里以jdk內(nèi)置Consumer 函數(shù)式接口示例:

原來的匿名內(nèi)部類寫法

Consumer<String> c = new Consumer<String>() {
    @Override
    public void accept(String str) {
        System.out.println("msg=" + str);
    }
};

新的Lambda表達式寫法

Consumer<String> c1 = (String str) -> {
    System.out.println("msg=" + str);
};

可以看到代碼簡潔了很多

再比Runnable是一個函數(shù)接口,創(chuàng)建線程可以這樣寫

Thread t = new Thread(() -> {
    System.out.print("Lambda!");
    System.out.print("Do Something...");
});
t.run();

更簡化的寫法

參數(shù)可以聲明類型,也可以根據(jù)類型推斷而省略

如上面的c1可以省略參數(shù)類型String

Consumer<String> c1 = (str) -> {
    System.out.println("msg=" + str);
};

但是如果有兩個參數(shù)不能只寫一個參數(shù)的類型,要么都寫類型,要么都不寫類型

只有一個參數(shù)時可以省略參數(shù)的小括號
Consumer<String> c2 = str -> {
    System.out.println("msg=" + str);
};
方法體只有一條語句時花括號和語句的分號都可以省略
Consumer<String> c3 = str -> System.out.println(str);
只有一條返回語句時return可以不寫

如返回一個String

Supplier<String> s1 = () -> {
    return "Lambad!";
};

可以寫成

Supplier<String> s2 = () -> "Lambad!";

返回對象可以寫成

Supplier<User> s3 = () -> new User("張三", 20);
使用方法引用時參數(shù)和箭頭符號都可以省略

前提是:接口定義的參數(shù) 和 方法的參數(shù)匹配

如:

Consumer<String> c4 = System.out::println;

accept.accept(String t) 的參數(shù)與 System.out.println(String x)的參數(shù)匹配

構(gòu)造器引用

Supplier<User> s4 = User::new;

靜態(tài)方法引用
User對象中有一個叫g(shù)etInstance的靜態(tài)方法

Supplier<User> s5 = User::getInstance;

多參數(shù)+返回
傳入兩個int值返回較大的一個

BiFunction<Integer, Integer, Integer> bf = (x, y) -> x > y ? x : y;
# 可以寫成
BiFunction<Integer, Integer, Integer> bf1 = Math::max;

原則就是讓編譯器能識別,不產(chǎn)生歧義!

Lambda表達式序列化問題

一個Lambda能否序列化, 要以它捕獲的參數(shù)以及target type能否序列化為準(zhǔn)。lambda表達式是可以序列化的,但是就像內(nèi)部類一樣,強烈建議不要對lambda表達式進行序列化。

ps:經(jīng)測試 使用方法引用時不能序列化

反對序列化內(nèi)部類(包括局部類和匿名類)。當(dāng)Java編譯器編譯某些構(gòu)造時,比如內(nèi)部類,它會創(chuàng)建合成構(gòu)造;這些是在源代碼中沒有相應(yīng)構(gòu)造的類、方法、字段和其他構(gòu)造。合成構(gòu)造使Java編譯器能夠在不更改JVM的情況下實現(xiàn)新的Java語言特性。然而,合成構(gòu)造可能因不同的Java編譯器實現(xiàn)而不同,這意味著.class文件也可能因不同的實現(xiàn)而不同。因此,如果您序列化一個內(nèi)部類,然后用不同的JRE實現(xiàn)反序列化它,您可能會有兼容性問題。有關(guān)編譯內(nèi)部類時生成的合成構(gòu)造的更多信息,請參閱獲取方法參數(shù)名稱一節(jié)中的隱式和合成參數(shù)一節(jié)。



接口默認方法和靜態(tài)方法

從Java 8 開始,接口中引入了 缺省方法(default method) 和 靜態(tài)方法 (static method) 的特性。

注意:接口中的方法銹蝕符號只能是 {抽象、默認、靜態(tài)} 中的一種

默認方法

默認方法就是接口可以有實現(xiàn)方法,不需要實現(xiàn)類主動去實現(xiàn)默認會自動繼承,當(dāng)接口中的默認方法不滿足要求時實現(xiàn)類可以對其進行重寫,調(diào)用時重寫的方法優(yōu)先于默認方法被調(diào)用。

默認方法用default關(guān)鍵字修飾,默認是public權(quán)限

添加此功能是為了實現(xiàn)向后兼容,默認方法不會破壞原有的接口實現(xiàn)還便于給程序添加新特性。比如某一天我們需要給線上某個接口增加一個方法,在 Java 8 之前,我們需要去改每一個實現(xiàn)類,就算這個實現(xiàn)根本用不到這個功能也得加一個空實現(xiàn),否則編譯不了,改動起來非常麻煩。Java 8 之后就只需要在接口上增加一個默認方法就可以了。

java不支持多繼承但是可以實現(xiàn)多個接口,有了默認方法之后我們可以將方法的相關(guān)邏輯寫到接口中這樣也能解決部分多繼承的問題。

繼承特性

interface A {
     // 默認方法
    default void say() {
        System.out.println("hello");
    }
}

class C1 implements A {

}

public static void main(String[] args) {
    C1 c1 = new C1();
    // 自動繼承
    c1.say();
}

輸出:

hello
子類方法優(yōu)先

C1重寫say方法

class C1 implements A {
    @Override
    public void say() {
        System.out.println("C1 hello");
    }
}

輸出:

C1 hello

子類的子類會優(yōu)先繼承父類的方法,父類沒有重寫時才會使用接口中的方法

C2類繼承C1類

class C2 extends C1{
    
}

public static void main(String[] args) {
   C2 c2 = new C2();
   c2.say();
}

輸出:

C1 hello
實現(xiàn)多個接口中有相同的方法時需指定

如子類實現(xiàn)兩個接口,兩個接口中有相同的方法(參數(shù)和方法名相同),實現(xiàn)類需要重寫該,否則編譯器不知道該調(diào)用哪一個

interface A {
    default void say() {
        System.out.println("[interface A] say hello");
    }
}

interface B {
    default void say() {
        System.out.println("[interface B] say hello");
    }
}

class C1 implements A, B {
    @Override
    public void say() {
        System.out.println("需要重寫say方法,否則編譯器不知道是調(diào)用 A.say 還是 B.say");
    }
}
顯示引用接口的默認實現(xiàn)

通過使用super,可以顯式的引用被繼承接口的默認實現(xiàn),語法如下:InterfaceName.super.methodName()

class C1 implements A, B {
    @Override
    public void say() {
        A.super.say();
    }
}

輸出:

[interface A] say hello

靜態(tài)方法

接口中的靜態(tài)方法和類中定義的靜態(tài)方法一樣,不屬于特定對象,所以它們不是實現(xiàn)接口的api的一部分,必須使用InterfaceName.staticMethod來調(diào)用它們。

接口中的所有方法聲明(包括靜態(tài)方法)都是隱式的public,因此可以省略public修飾符

interface A {
    static String STR = "靜態(tài)字段可以被繼承";
    static void sayA() {
        System.out.println("static sayA");
    }
}

class C1 implements A {
}

//main
public static void main(String[] args) {
    // 通過接口名稱調(diào)用接口的靜態(tài)方法
    A.sayA();

    // 不能通過子類實例調(diào)用 new C1().sayA()
    // 也不能通過子類調(diào)用 C1.sayA()
    
    //可以通過子類實例獲取靜態(tài)字段值
    System.out.println(new C1().STR);
}

實現(xiàn)接口的類或者子接口不會繼承接口中的靜態(tài)方法,只能是靜態(tài)方法所屬的接口來調(diào)用。但是接口中靜態(tài)字段可以被繼承(默認用public static final修飾)。

在java8中很多接口中都使用默認方法和靜態(tài)方法進行了增強,如:Comparator,接口靜態(tài)方法適合于提供實用方法,例如空檢查、集合排序等。



函數(shù)式接口

只有一個抽象方法的接口

可以有多個默認方法或者靜態(tài)方法等

接口默認繼承java.lang.Object,所以如果接口顯示聲明覆蓋了Object類中的public方法,那么 也不算抽象方法,因為任何接口的實現(xiàn)都會從其父類Object或其它地方獲得這些方法的實現(xiàn)。

如:

@FunctionalInterface
public interface FunctionalInterfaceT1<T> {

    // 抽象方法
    void doSomething(T t);

    // Object類中的方法
    boolean equals(Object obj);

    // 默認方法
    default void printT(T t) {
        System.out.println(t);
    }

    // 默認方法2
    default void printT2(T t) {
        System.out.println(t);
    }

    // 靜態(tài)方法
    public static void print() {
        System.out.println("FunctionalInterfaceT1.print");
    }
}

@FunctionalInterface 注解

該注解不是必須的,如果一個接口符合"函數(shù)式接口"定義,那么加不加該注解都沒有影響。加上該注解能夠更好地讓編譯器進行檢查。如果編寫的不是函數(shù)式接口,但是加上了@FunctionInterface,那么編譯器會報錯。

默認方法不能覆蓋Object中的方法

接口不能實現(xiàn)Object的toString、equals、hashCode等方法,因為接口多繼承如果多個接口都實現(xiàn)了equals方法會不知道調(diào)用哪一個

編譯過不了

內(nèi)置的核心函數(shù)式接口

java8中內(nèi)置的函數(shù)式接口位于包:java.util.function

4大常用的函數(shù)式接口

Consumer<T>

消費型 接口

方法:void accept(T t)
用來消費 T 類型的對象

Supplier<T>

供給型 接口

方法: T get()
提供 T 類型的對象

Function<T,R>

函數(shù)型 接口

方法: R apply(T t)
對類型為 T 的對象進行操作返回 R 類型的對象

Predicate<T>

斷言型 接口

方法: boolean test(T t)
判斷 T 類型的對象是否滿足某些約束

其他類型的函數(shù)式接口

也都在 java.util.function 包下,大體上都差不多,記住上面常用的4個就行了

BiFunction<T, U, R>

參數(shù)為 T,U,返回值為 R
方法為 R apply(T t, U u)

UnaryOperator<T> (Function子接口)

參數(shù)為 T,對參數(shù)為T的對象進行一元操作,并返回T類型結(jié)果
方法為 T apply(T t)

BinaryOperator<T> (BiFunction子接口)

參數(shù)為T,對參數(shù)為T得對象進行二元操作,并返回T類型得結(jié)果
方法為 T apply(T t1, T t2)

BiConsumer(T, U)

參數(shù)為 T,U 無返回值
方法為 void accept(T t, U u)

BiPredicate<T, U>

參數(shù)為 T,U 返回boolean值
方法為: boolean test(T t, U u)

ToIntFunction<T>、ToLongFunction<T>、ToDoubleFunction<T>

參數(shù)類型為T,返回值分別為int,long,double
分別計算int,long,double的函數(shù)
方法為:int applyAsInt(T value);long applyAsLong(T t, U u)

IntFunction<R>、LongFunction<R>、DoubleFunction<R>

參數(shù)分別為int,long,double,返回值為R。
方法為: R apply(int value); R apply(long value);



方法引用

方法引用是一個新特性,方法類似指針一樣可以被直接引用。
新的操作符"::"(兩個冒號)用來引用 或者 實例 的方法。

方法引用可以使語言的構(gòu)造更緊湊簡潔,減少冗余代碼。

靜態(tài)方法引用

BiFunction<Integer, Integer, Integer> bf = Math::max;
System.out.println(bf.apply(1, 3));

實例方法引用

Consumer<String> c = System.out::println;
c.accept("hello");

任意實例引用

String[] stringArray = { "Barbara", "James", "Mary", "John", "Patricia", "Robert", "Michael", "Linda" };
Arrays.sort(stringArray, String::compareToIgnoreCase);
Arrays.stream(stringArray).forEach(System.out::println);

可以引用任意的一個類型的實例。等價的lambda表達式的參數(shù)列表為(String a, String b),方法引用會調(diào)用a.compareToIgnoreCase(b)。

構(gòu)造函數(shù)引用

Supplier<User> s = User::new;

對構(gòu)造函數(shù)的引用類似對靜態(tài)方法的引用,只不過方法名是new。 一個類有多個構(gòu)造函數(shù), 會根據(jù)target type選擇最合適的構(gòu)造函數(shù)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容