一 lambda 表達式初探
關(guān)于定義:Lambda 允許把函數(shù)作為一個方法的參數(shù)(函數(shù)作為參數(shù)傳遞進方法中)。使用 Lambda 表達式可以使代碼變的更加簡潔緊湊。
上面這句話,在不了解代碼的時候,讀起來有點生澀。在了解完相關(guān)特性之后,再回來看這個定義,才能更好的理解。
/**
* @author gaopeng@doctorwork.com
* @description
* @date 2019-06-05 9:00
**/
public interface MathOperation {
int operation(int a, int b);
}l
===================================
/**
* @author gaopeng@doctorwork.com
* @description
* @date 2019-06-05 9:03
**/
public class LambdaTest {
public static void main(String[] args) {
// 匿名函數(shù)與 lambda 表達式的對比,將匿名函數(shù)的聲明完全簡化,僅僅保留了入?yún)⒘斜砗途唧w實現(xiàn)的表達式,
// 如果函數(shù)內(nèi)方法的實現(xiàn)僅有一行,那么這個 lambda 表達式看起來就十分的簡潔
MathOperation addition = (int a, int b) -> a + b;
MathOperation normalAddition = new MathOperation() {
@Override
public int operation(int a, int b) {
return a + b;
}
};
// 不使用參數(shù)的類型聲明
MathOperation substract = ((a, b) -> a - b);
// 在大括號中包含返回值
MathOperation multiplication = (((a, b) -> { return a * b;}));
// 最簡潔的寫法:沒有大括號、返回語句
MathOperation division = (a, b) -> a / b;
/**
* lambda 表達式的變量作用域
*/
}
}
面對數(shù)學(xué)操作 MathOperation 接口,要想使用它,我們有兩種大的方式分類:
- 獨立的類,實現(xiàn)這個接口并單獨使用
- 使用匿名函數(shù)
老的方法,使用匿名函數(shù)的話,寫法就是 demo 中的樣式:
MathOperation normalAddition = new MathOperation() {
@Override
public int operation(int a, int b) {
return a + b;
}
};
這個場景,是 JDK8 之后 lambda 表達式的適用場景:
// 匿名函數(shù)與 lambda 表達式的對比,將匿名函數(shù)的聲明完全簡化,僅僅保留了入?yún)⒘斜砗途唧w實現(xiàn)的表達式,
// 如果函數(shù)內(nèi)方法的實現(xiàn)僅有一行,那么這個 lambda 表達式看起來就十分的簡潔
MathOperation addition = (int a, int b) -> a + b;
二 函數(shù)式接口
前面一節(jié)的例子中,自定義接口 MathOperation,有且僅有一個方法定義,所有實現(xiàn)了這個接口的類都被強制要求實現(xiàn)該方法。這樣的接口范例,在 JDK8 之中有了一個新的官方定義 函數(shù)式接口,也叫 SAM 接口-Single Abstract Method Interface,并得到了一個專有的注解
@FunctionalInterface
如果自定義的接口里面,加上了這個注解,在編譯階段就會檢查這個接口是否符合 函數(shù)式接口的定義規(guī)范,如果不規(guī)范,那么這個類就無法完成編譯。
那么問題來了,為什么要定義這樣一個注解呢,只要接口使用了這個注解并通過了編譯檢查,那么這些類型的接口,就都可以方便地使用 lambda 表達式編寫,使用 lambda 表達式完成匿名內(nèi)部類的簡寫。
// 自定義比較器
Comparator<Student> studentComparator = new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.getAge() - o2.getAge();
}
};
// 使用lambda表達式完成的自定義比較器
Comparator<Student> lambdaComparator = (student1, student2) -> student1.getAge() - student2.getAge();
常用的自定義比較器 Comparator 在 JDK8 的版本上,都被增加了這個 @FunctionalInterface 注解。
函數(shù)式接口的別名讓我們就可以很清楚地了解到這類接口的要求:只有一個抽象方法的聲明。如果使用了注解還聲明了多個抽象方法,那么就會報錯,無法通過編譯檢查。
那么除了抽象方法,函數(shù)式接口還是允許擁有一些其他的東西:
- 接口默認方法(JDK8 特性)-接口默認方法也不會要求實現(xiàn)接口的類去實現(xiàn)它
- 靜態(tài)方法-靜態(tài)方法不能是抽象方法
- Object 基類中的 public 方法,java 中所有的類都繼承自基類
這一節(jié)我們使用了 JDK 自帶的 Comparator 接口來做示例,除了這個,還有 JDK 中的 Runnable 接口也被 JDK 標注上了函數(shù)式接口注解,這表示我們在使用 runnable 接口的時候,一樣可以順當?shù)乩?lambda 表達式。
除此之外,我們的自定義接口也可以使用了,在第一節(jié)使用到的 MathOperation 接口,雖然沒有加上這個注解,但是其接口使用,完全也是符合 SAM 標準,即一個單一的抽象方法聲明接口。我可以簡單完善一下這個接口:
@FunctionalInterface
public interface MathOperation {
int operation(int a, int b);
}
如果去掉函數(shù)式接口注解,并在接口里聲明另一個方法,那么這個接口就無法順利地使用 lambda 表達式。使用則會報錯
Multiple non-overriding abstract methods found in xxx
三 lambda 表達式變量作用域
1 lambda 表達式中不允許聲明一個與局部變量同名的參數(shù)或者局部變量
Student demo = new Student();
MathOperation mathOperation = (demo, demo2) -> demo - demo2;
在這段示例代碼中,表達式里面的 demo 變量會報錯,因為這個變量名在上一行代碼中已經(jīng)被其他的對象聲明給占用了。
2 lambda 表達式中可以直接訪問外層的局部變量
int outNumber = 12;
Thread thread1 = new Thread(() -> System.out.println(outNumber));
Thread thread2 = new Thread(() -> System.out.println(params));
上面的示例代碼,outNumber 是一個方法內(nèi)的局部變量,params 則是一個類的靜態(tài)成員。這些類型的參數(shù)均可以在 lambda 表達式中引用。
3 lambda 表達式中被引用的變量值不可以被更改
Thread thread3 = new Thread(() -> System.out.println(outNumber--));
這樣一行代碼是無法通過編譯檢查的,在表達式中我嘗試修改方法的局部變量 outNumber。然后 idea 就提示我: variable used in lambda expression should be final or effectively final。即 lambda 表達式中使用的變量只能是 final 類型或者隱式的 final 類型。
最明顯的好處:final類型的參數(shù)傳入 lambda 表達式的使用,表示這個操作是 線程安全的,我們可以放心的時候而不必擔心并發(fā)問題。需要注意的是,如果傳入的不是基礎(chǔ)數(shù)據(jù)類型,是一個對象引用,那么修改對象引用內(nèi)的成員是對的。
Thread thread4 = new Thread(() -> demo.setName("gaop"));
這樣的使用方法,需要注意產(chǎn)生并發(fā)修改的場景。