1. 引言
我希望通過這一篇文章、可以讓讀者全面了解Lambda表達(dá)式、也許不夠全面、我在盡力完善它、也希望你能留下寶貴意見、在下方留言。文章有點長、請耐心看完。
2. 描述
- 可以將 Lambda 表達(dá)式理解為簡潔的表示可傳遞匿名函數(shù)的一種方式:它沒有名稱,但它有參數(shù)列表、函數(shù)主體、返回類型,可能還有一個可拋出的異常列表。
- Lambda 表達(dá)式是實現(xiàn)行為參數(shù)化的一種方式,這種方式比起使用匿名內(nèi)部類的方式更加的簡潔、易讀。
3. 組成
- 參數(shù)
- 箭頭
- 主體
例如
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight())
4. Lambda 語法
- (parameters) -> expression
- (parameters) -> { statements; }
5.有效的 Lambda 表達(dá)式
/* 表示有一個 String 類型的入?yún)?,且返回一個 int 類型的結(jié)果。 */
(String s) -> s.length()
/* 表示有一個 Apple 類型的入?yún)?,且返回一個 boolean 類型的結(jié)果。 */
(Apple a) -> a.getWeight() > 150
/* 表示有兩個 int 類型的入?yún)?,且沒有返回值(返回 void )。 */
(int x, int y) -> {
System.out.println("Result:");
System.out.println(x+y);
}
/* 表示沒有入?yún)ⅲ曳祷匾粋€ int 類型的結(jié)果。 */
() -> 1
/* 表示有兩個 Apple 類型的入?yún)ⅲ曳祷匾粋€ int 類型的結(jié)果,比較兩個蘋果的重量。 */
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight())
6. 使用 Lambda 表達(dá)式
6.1 函數(shù)式接口
- 定義:只定義了一個抽象方法的接口稱為函數(shù)式接口。接口中可以包含多個 default 方法,只要接口只定義了一個抽象方法、那么該接口就是函數(shù)式接口,比如java.lang.Runnable 類和 java.util.Comparator 類。
- 作用:Lambda 表達(dá)式允許你直接以內(nèi)聯(lián)的形式為函數(shù)式接口的抽象方法提供實現(xiàn),且將整個表達(dá)式作為函數(shù)式接口的實例(具體來說、Lambda表達(dá)式是函數(shù)式接口的一個實例)。
- 備注:可以使用 @FunctionalInterface 注解注明接口是函數(shù)式接口,提高代碼的可讀性。若接口包含多個抽象方法(非函數(shù)式接口),使用該注解將會編譯報錯。
示例
public static void main(String[] args) {
Runnable r1 = () -> System.out.println(" 使用 Lambda 表達(dá)式。 ");
Runnable r2 = new Runnable() {
@Override
public void run() {
System.out.println(" 使用匿名類。 ");
}
};
process(r1);
process(r2);
}
private static void process(Runnable r1) {
if (Objects.nonNull(r1)) {
r1.run();
}
}
結(jié)論: 函數(shù)式接口通過使用 Lambda 表達(dá)式的方式創(chuàng)建接口的一個實例,比通過匿名類的方式,代碼更加的簡潔大方。
6.2 常用函數(shù)式接口
Java API中已經(jīng)有幾個函數(shù)式接口,如Comparable、Runnable、Callable
6.2.1 Predicate
java.util.function.Predicate<T> 接口是一個函數(shù)式接口,只包含一個test()抽象方法,接受泛型 T 對象,返回一個 boolean 值。
/**
* 定義學(xué)生類
*
* @author : sungm
* @since : 2019/8/7
*/
public class Student {
private String id;
private String name;
private String sex;
private Integer age;
private Double weight;
private Long height;
//省略了 get/set方法, 默認(rèn)構(gòu)造函數(shù), 全構(gòu)造函數(shù)
}
/**
* 主類
*
* @author : sungm
* @since : 2019-08-23 16:24
*/
public class Main {
public static void main(String[] args) {
/* 通過Lambda表達(dá)式創(chuàng)建一個Predicate實例(test方法表達(dá)式為年齡大于10歲) */
Predicate<Student> agePredicate = (Student student) -> student.getAge() > 10;
/* 通過Lambda表達(dá)式創(chuàng)建一個Predicate實例(test方法表達(dá)式為身高高于165cm) */
Predicate<Student> heightPredicate = (Student student) -> student.getHeight() > 165L;
/* 創(chuàng)建一些數(shù)據(jù) */
Student sungm = new Student("1", "sungm", "1", 25, 69.1D, 178L);
Student sunzm = new Student("2", "sunzm", "1", 22, 60.0D, 176L);
Student sunhw = new Student("3", "sunhw", "1", 1 , 4.1D , 53L);
Student sunll = new Student("4", "sunll", "2", 24, 55.2D, 168L);
List<Student> students = Arrays.asList(sungm, sunzm, sunhw, sunll);
/* 篩選出年齡大于10歲的學(xué)生 */
List<Student> ageFilterStudents = students.stream().filter(agePredicate).collect(Collectors.toList());
/* 篩選出年齡小于或等于10歲的學(xué)生 */
List<Student> ageNegateFilterStudents = students.stream().filter(agePredicate.negate()).collect(Collectors.toList());
/* 篩選出身高高于165cm的學(xué)生 */
List<Student> heightFilterStudents = students.stream().filter(heightPredicate).collect(Collectors.toList());
/* 篩選出 年齡大于10歲 且 身高高于165cm 的學(xué)生 */
List<Student> ageAndHeightFilterStudents = students.stream().filter(agePredicate.and(heightPredicate))
.collect(Collectors.toList());
/* 篩選出 年齡大于10歲 或 身高高于165cm 的學(xué)生 */
List<Student> ageOrHeightFilterStudents = students.stream().filter(agePredicate.or(heightPredicate))
.collect(Collectors.toList());
}
}
注意: Stream.filter()方法接收一個Predicate<T>入?yún)?,可用于Java 8 的 Stream 中,其源碼如下
public interface Stream<T> extends BaseStream<T, Stream<T>> {
/**
* Returns a stream consisting of the elements of this stream that match
* the given predicate.
*
* <p>This is an <a href="package-summary.html#StreamOps">intermediate
* operation</a>.
*
* @param predicate a <a href="package-summary.html#NonInterference">non-interfering</a>,
* <a href="package-summary.html#Statelessness">stateless</a>
* predicate to apply to each element to determine if it
* should be included
* @return the new stream
*/
Stream<T> filter(Predicate<? super T> predicate);
//其他方法省略
...
}
6.2.2 Consumer
java.util.function.Consumer<T> 接口是一個函數(shù)式接口,只包含一個accept()抽象方法,接受泛型 T 對象,沒有返回值(void)。
public static void main(String[] args) {
//造一批假數(shù)據(jù)、用于測試
List<Student> students = createStudents();
//定義一個Consumer
Consumer<Student> studentConsumer = (Student student) -> student.setOverWeight(student.getWeight() > 50);
for (Student student : students) {
//消費(fèi)student
studentConsumer.accept(student);
}
}
6.2.3 Function
java.util.function.Function<T, R>接口定義了一個叫作apply的方法,它接受一個
泛型T的對象,并返回一個泛型R的對象。
public static void main(String[] args) {
//造一批假數(shù)據(jù)、用于測試
List<Student> students = Test1.createStudents();
//定義一個function, 其接收一個student對象,返回student的名稱
Function<Student, String> function = Student::getName;
List<String> studentNames = new ArrayList<>();
for (Student student : students) {
studentNames.add(function.apply(student));
}
System.out.println(studentNames);
}
注意
任何函數(shù)式接口都不允許拋出受檢異常(checked exception)。如果你需要 Lambda 表達(dá)式來拋出異常,有兩種辦法:定義一個自己的函數(shù)式接口,并聲明受檢異常,或者把Lambda 包在一個try/catch塊中。
7. 類型檢查、類型推斷及限制
7.1 類型檢查
Lambda 類型是從使用 Lambda 的上下文推斷出來的,上下文中 Lambda 表達(dá)式需要的類型成為目標(biāo)類型。

7.2 同樣的 Lambda , 不同的函數(shù)式接口
有了目標(biāo)類型的概念,同一個 Lambda 表達(dá)式就可以與不同的函數(shù)式接口聯(lián)系起來,只要他們的抽象方法能夠兼容。
舉例
Callable<Integer> c = () -> 42;
PrivilegedAction<Integer> p = () -> 42;
特殊的void兼容規(guī)則
如果一個Lambda的主體是一個語句表達(dá)式, 它就和一個返回void的函數(shù)描述符兼容(當(dāng)
然需要參數(shù)列表也兼容)
舉例
// Predicate返回了一個boolean
Predicate<String> p = s -> list.add(s);
// Consumer返回了一個void
Consumer<String> b = s -> list.add(s);
7.3 類型推斷
你還可以進(jìn)一步簡化你的代碼。Java編譯器會從上下文(目標(biāo)類型)推斷出用什么函數(shù)式接口來配合 Lambda 表達(dá)式,這意味著它也可以推斷出適合 Lambda 的簽名,因為函數(shù)描述符可以通過目標(biāo)類型來得到。這樣做的好處在于,編譯器可以了解 Lambda 表達(dá)式的參數(shù)類型,這樣就可以在 Lambda 語法中省去標(biāo)注參數(shù)類型。
舉例
// 沒有推斷類型
Comparator<Apple> c = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
// 有推斷類型
Comparator<Apple> c = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight());
注意
有時候顯式寫出類型更易讀,有時候去掉它們更易讀。沒有什么法則說哪種更好;對于如何讓代碼更易讀,你必須做出自己的選擇。
7.4 使用局部變量
我們迄今為止所介紹的所有Lambda表達(dá)式都只用到了其主體里面的參數(shù)。但Lambda表達(dá)式也允許使用自由變量(不是參數(shù),而是在外層作用域中定義的變量),就像匿名類一樣。
舉例
int x = 1;
// Lambda 表達(dá)式使用了自由變量 x
IntFunction<Integer> function = y -> x + y;
說明
Lambda 可以無限制的獲取實例變量和靜態(tài)變量,但局部變量必須顯示聲明為 final 類型、或者事實上是 final 類型。換句話說,Lambda表達(dá)式只能捕獲指派給它們的局部變量一次。(注:捕獲實例變量可以被看作捕獲最終局部變量this。)
為什么局部變量有這些限制
- 實例變量保存在堆中,局部變量保存在棧中。如果 Lambda 可以直接訪問局部變量,則使用 Lambda的線程,它可能會在分配該局部變量的線程收回該局部變量之后訪問該局部變量。因此,Java在訪問自由局部變量時,實際上是在訪問它的副本,而不是訪問原始變量。如果局部變量僅僅賦值一次那就沒有什么區(qū)別了——因此就有了這個限制。
2.這一限制不鼓勵你使用改變外部變量的典型命令式編程模式