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ù)。