Java 9 好像也快出了圾浅,不過我連Java 8的新特性都還沒認(rèn)真研究過疆股,所以這幾篇文章就是來介紹Java 8的新特性的锅必。首先盼樟,第一個重要的特性就是傳說中的lambda表達(dá)式了氢卡,雖然初學(xué)可能覺得這東西很難理解,但是一旦學(xué)會了晨缴,你就會發(fā)現(xiàn)離不開它了译秦。
在現(xiàn)代編程語言中,lambda表達(dá)式這個語言特性已經(jīng)成為了標(biāo)配氯檐。當(dāng)然不同語言中的lambda表達(dá)式概念和實(shí)現(xiàn)形式可能會稍有差別慨亲。不過不管在哪種語言中鹰椒,我們都可以把lambda表達(dá)式簡單的理解為匿名函數(shù)。在Java中阁吝,lambda表達(dá)式和只有一個方法的接口是差不多的,所以這樣的接口又叫做SAM接口(Single Abstract Method interfaces)械拍。
初識lambda表達(dá)式
說了半天突勇,下面先來看看實(shí)際例子吧装盯。我們在編寫多線程代碼的時候,常常需要編寫實(shí)現(xiàn)Runnable
的類甲馋。有時候?yàn)榱耸∈鹿∧危覀儠懗赡涿麅?nèi)部類的形式。
Runnable task1 = new Runnable() {
@Override
public void run() {
System.out.println("任務(wù)一");
}
};
匿名內(nèi)部類不好理解定躏,而且寫起來也非常丑陋账磺。由于Runnable
接口內(nèi)只有一個方法,所以它是一個SAM接口痊远,我們可以用lambda表達(dá)式改寫它垮抗。那么改寫之后是什么樣子的呢?不要驚訝拗引,只需要一行代碼借宵。
Runnable task2 = () -> System.out.println("任務(wù)二");
因此,我們可以看到lambda表達(dá)式的簡潔之處矾削。下面就來詳細(xì)介紹一下lambda表達(dá)式的形式壤玫。
lambda表達(dá)式形式
lambda表達(dá)式可以用在需要SAM接口對象的地方,由于編譯器這時候知道所需要的接口和對應(yīng)方法的簽名哼凯。所以lambda表達(dá)式的類型聲明可以省略欲间,具體類型由編譯器自行推斷。lambda表達(dá)式的基本形式如下断部。
(參數(shù)列表) -> {方法體}
為了方便說明猎贴,我這里定義了一組接口,用于之后的lambda表達(dá)式實(shí)現(xiàn)蝴光。
@FunctionalInterface
interface Interface1 {
void f();
}
@FunctionalInterface
interface Interface2 {
void f(int a);
}
@FunctionalInterface
interface Interface3 {
void f(int a, int b);
}
@FunctionalInterface
interface Interface4 {
int f(int a, int b);
}
無參lambda
先來看看無參數(shù)的lambda表達(dá)式她渴。特別地,如果方法體只有一行代碼蔑祟,可以省略方法體的大括號趁耗。
Interface1 interface1 = new Interface1() {
@Override
public void f() {
System.out.println("f");
}
};
Interface1 lambda1 = () -> {
System.out.println("f");
};
//如果方法體只有一行代碼,可以省略大括號
Interface1 lambda1a = () -> System.out.println("f");
多個參數(shù)的lambda
我們可以看到由于編譯器可以進(jìn)行類型推斷疆虚,所以lambda表達(dá)式的參數(shù)列表不需要參數(shù)類型聲明苛败,而且由于我只有一行代碼,所以大括號也省略了径簿。
//多個參數(shù)lambda
Interface3 interface3 = new Interface3() {
@Override
public void f(int a, int b) {
System.out.println(a + b);
}
};
Interface3 lambda3 = (a, b) -> System.out.println(a + b);
帶返回值的lambda
帶返回值的lambda表達(dá)式其實(shí)和普通的一樣罢屈,只不過需要在方法體的最后使用return
語句。值得注意的是最后一個例子篇亭,如果方法體本身足夠簡單缠捌,可以直接將返回值表達(dá)式寫在lambda表達(dá)式的右邊,編譯器也會正確對待這種情況译蒂。
//帶有返回值的lambda
Interface4 interface4 = new Interface4() {
@Override
public int f(int a, int b) {
return a + b;
}
};
Interface4 lambda4 = (a, b) -> {
return a + b;
};
Interface4 lambda4a = (a, b) -> a + b;
單個參數(shù)的lambda
如果lambda表達(dá)式只有一個參數(shù)曼月,那么參數(shù)列表的小括號也可以省略肃叶,例如下面的第二個例子。下面第三個例子演示了另一個新語法方法引用十嘿,如果lambda表達(dá)式的形式是單個參數(shù)a -> 某個只有一個參數(shù)的方法(a)
因惭,那么就可以簡寫成方法引用的形式類::方法
。方法引用是一個新語法绩衷,如果見到了不要奇怪蹦魔,這也是正確的Java代碼。
//一個參數(shù)的lambda
Interface2 interface2 = new Interface2() {
@Override
public void f(int a) {
System.out.println(a);
}
};
Interface2 lambda2 = (a) -> System.out.println(a);
Interface2 lambda2a = a -> System.out.println(a);
Interface2 methodReference = System.out::println;
函數(shù)接口
@FunctionalInterface
注解
如果查看JDK源碼的一些接口咳燕,會發(fā)現(xiàn)它們上面添加了@FunctionalInterface
注解勿决。例如上面提到的Runnable
接口。
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
這個注解用于標(biāo)注函數(shù)接口招盲,也就是上面提到的SAM接口低缩。如果一個接口中包含了不止一個方法,那么函數(shù)接口注解就會觸發(fā)編譯錯誤曹货。這個功能類似于@Override
注解咆繁。當(dāng)然并不是說要使用lambda表達(dá)式必須標(biāo)記這個注解,只要接口中只有一個方法顶籽,那么就可以使用lambda表達(dá)式玩般。
其實(shí)函數(shù)接口可以包括不止一個方法,除了一個普通的抽象方法之外礼饱,函數(shù)接口還可以包含一個在java.lang.Object
類中聲明的方法坏为。Java 8新增了默認(rèn)方法特性,如果接口的實(shí)現(xiàn)類沒有實(shí)現(xiàn)java.lang.Object
類中的這些基本方法镊绪,那么就會調(diào)用java.lang.Object
類中的實(shí)現(xiàn)作為默認(rèn)實(shí)現(xiàn)匀伏。
@FunctionalInterface
interface Interface5 {
void f();
String toString();
}
預(yù)定義的函數(shù)接口
在java.util.function
包下定義了大量預(yù)定義的函數(shù)接口,它們包含了各種各樣的形式蝴韭,基本涵蓋了編程的各個方面够颠。同學(xué)們最好對這些函數(shù)接口的形式稍作了解,以后碰到的時候就不會一頭霧水了万皿。
lambda表達(dá)式應(yīng)用
編寫線程
前面提到了摧找,可以使用lambda表達(dá)式簡化線程代碼的編寫核行,不再需要冗長的匿名內(nèi)部類牢硅。
//編寫線程
Runnable task = () -> System.out.println("簡單的任務(wù)");
Executor executor = Executors.newCachedThreadPool();
executor.execute(task);
編寫比較器
比方說我們有一個學(xué)生類,有姓名和年齡兩個字段芝雪。如果需要按照某種規(guī)則來進(jìn)行排序减余,可以使用比較器Comparator
接口來比較,利用lambda表達(dá)式同樣可以簡化這部分代碼的編寫惩系。
class Student {
private String name;
private int age;
//其余方法已省略
}
可以看到比較規(guī)則只需要一條lambda表達(dá)式即可傳入位岔,非常方便如筛。后面兩個例子是Comparator
接口新增的工具方法,可以幫助我們簡化比較代碼的編寫抒抬,這里使用了方法引用來簡化代碼杨刨。comparing
等方法的返回值仍然是Comparator
,所以可以鏈?zhǔn)秸{(diào)用進(jìn)行多級比較擦剑。
List<Student> students = new ArrayList<>();
students.add(new Student("yitian", 25));
students.add(new Student("anna", 26));
students.add(new Student("wang5", 24));
students.sort((a, b) -> a.getAge() - b.getAge());
System.out.println(students);
students.sort(Comparator.comparing(Student::getName));
System.out.println(students);
students.sort(
Comparator.comparing(Student::getName)
.thenComparing(Student::getAge));
System.out.println(students);