Java 9 好像也快出了,不過我連Java 8的新特性都還沒認真研究過,是以這幾篇文章就是來介紹Java 8的新特性的。首先,第一個重要的特性就是傳說中的lambda表達式了,雖然初學可能覺得這東西很難了解,但是一旦學會了,你就會發現離不開它了。
在現代程式設計語言中,lambda表達式這個語言特性已經成為了标配。當然不同語言中的lambda表達式概念和實作形式可能會稍有差别。不過不管在哪種語言中,我們都可以把lambda表達式簡單的了解為匿名函數。在Java中,lambda表達式和隻有一個方法的接口是差不多的,是以這樣的接口又叫做SAM接口(Single Abstract Method interfaces)。
初識lambda表達式
說了半天,下面先來看看實際例子吧。我們在編寫多線程代碼的時候,常常需要編寫實作
Runnable
的類。有時候為了省事,我們會寫成匿名内部類的形式。
Runnable task1 = new Runnable() {
@Override
public void run() {
System.out.println("任務一");
}
};
匿名内部類不好了解,而且寫起來也非常醜陋。由于
Runnable
接口内隻有一個方法,是以它是一個SAM接口,我們可以用lambda表達式改寫它。那麼改寫之後是什麼樣子的呢?不要驚訝,隻需要一行代碼。
Runnable task2 = () -> System.out.println("任務二");
是以,我們可以看到lambda表達式的簡潔之處。下面就來詳細介紹一下lambda表達式的形式。
lambda表達式形式
lambda表達式可以用在需要SAM接口對象的地方,由于編譯器這時候知道所需要的接口和對應方法的簽名。是以lambda表達式的類型聲明可以省略,具體類型由編譯器自行推斷。lambda表達式的基本形式如下。
(參數清單) -> {方法體}
為了友善說明,我這裡定義了一組接口,用于之後的lambda表達式實作。
@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
先來看看無參數的lambda表達式。特别地,如果方法體隻有一行代碼,可以省略方法體的大括号。
Interface1 interface1 = new Interface1() {
@Override
public void f() {
System.out.println("f");
}
};
Interface1 lambda1 = () -> {
System.out.println("f");
};
//如果方法體隻有一行代碼,可以省略大括号
Interface1 lambda1a = () -> System.out.println("f");
多個參數的lambda
我們可以看到由于編譯器可以進行類型推斷,是以lambda表達式的參數清單不需要參數類型聲明,而且由于我隻有一行代碼,是以大括号也省略了。
//多個參數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表達式其實和普通的一樣,隻不過需要在方法體的最後使用
return
語句。值得注意的是最後一個例子,如果方法體本身足夠簡單,可以直接将傳回值表達式寫在lambda表達式的右邊,編譯器也會正确對待這種情況。
//帶有傳回值的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;
單個參數的lambda
如果lambda表達式隻有一個參數,那麼參數清單的小括号也可以省略,例如下面的第二個例子。下面第三個例子示範了另一個新文法方法引用,如果lambda表達式的形式是
單個參數a -> 某個隻有一個參數的方法(a)
,那麼就可以簡寫成方法引用的形式
類::方法
。方法引用是一個新文法,如果見到了不要奇怪,這也是正确的Java代碼。
//一個參數的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;
函數接口
@FunctionalInterface
注解
@FunctionalInterface
如果檢視JDK源碼的一些接口,會發現它們上面添加了
@FunctionalInterface
注解。例如上面提到的
Runnable
接口。
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
這個注解用于标注函數接口,也就是上面提到的SAM接口。如果一個接口中包含了不止一個方法,那麼函數接口注解就會觸發編譯錯誤。這個功能類似于
@Override
注解。當然并不是說要使用lambda表達式必須标記這個注解,隻要接口中隻有一個方法,那麼就可以使用lambda表達式。
其實函數接口可以包括不止一個方法,除了一個普通的抽象方法之外,函數接口還可以包含一個在
java.lang.Object
類中聲明的方法。Java 8新增了預設方法特性,如果接口的實作類沒有實作
java.lang.Object
類中的這些基本方法,那麼就會調用
java.lang.Object
類中的實作作為預設實作。
@FunctionalInterface
interface Interface5 {
void f();
String toString();
}
預定義的函數接口
在
java.util.function
包下定義了大量預定義的函數接口,它們包含了各種各樣的形式,基本涵蓋了程式設計的各個方面。同學們最好對這些函數接口的形式稍作了解,以後碰到的時候就不會一頭霧水了。

lambda表達式應用
編寫線程
前面提到了,可以使用lambda表達式簡化線程代碼的編寫,不再需要冗長的匿名内部類。
//編寫線程
Runnable task = () -> System.out.println("簡單的任務");
Executor executor = Executors.newCachedThreadPool();
executor.execute(task);
編寫比較器
比方說我們有一個學生類,有姓名和年齡兩個字段。如果需要按照某種規則來進行排序,可以使用比較器
Comparator
接口來比較,利用lambda表達式同樣可以簡化這部分代碼的編寫。
class Student {
private String name;
private int age;
//其餘方法已省略
}
可以看到比較規則隻需要一條lambda表達式即可傳入,非常友善。後面兩個例子是
Comparator
接口新增的工具方法,可以幫助我們簡化比較代碼的編寫,這裡使用了方法引用來簡化代碼。
comparing
等方法的傳回值仍然是
Comparator
,是以可以鍊式調用進行多級比較。
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);