課程重點:
1 函數式接口
函數式接口的概念
函數式接口的判斷
@FunctionalInterface
2 lambda表達式的語法
基礎的語法
語法的精簡(參數、方法體)
3 函數引用
靜態、非靜態、構造方法的引用
對象方法的特殊引用
22.1. Lambda表達式的簡介
22.1.1. Lambda表達式的概念
lambda表達式, 是Java8的一個新特性, 也是Java8中最值得學習的新特性之一。
lambda表達式, 從本質來講, 是一個匿名函數。 可以使用使用這個匿名函數, 實現接口中的方法。 對接口進行非常簡潔的實現, 從而簡化代碼。
22.1.2. Lambda表達式的使用場景
通常來講, 使用lambda表達式, 是爲了簡化接口實現的。
關於接口實現, 可以有很多種方式來實現。 例如: 設計接口的實現類、 使用匿名內部類。 但是lambda表達式, 比這兩種方式都簡單。
/**
* @Description
*/
public class Program {
public static void main(String[] args) {
// 無參、無返回值的函數式接口
interfaceImpl();
}
private static void interfaceImpl() {
// 1. 使用顯式的實現類對象
SingleReturnSingleParameter parameter1 = new Impl();
// 2. 使用匿名內部類實現
SingleReturnSingleParameter parameter2 = new SingleReturnSingleParameter() {
@Override
public int test(int a) {
return a * a;
}
};
// 3. 使用lambda表達式實現
SingleReturnSingleParameter parameter3 = a -> a * a;
System.out.println(parameter1.test(10));
System.out.println(parameter2.test(10));
System.out.println(parameter3.test(10));
}
private static class Impl implements SingleReturnSingleParameter {
@Override
public int test(int a) {
return a * a;
}
}
}
22.1.3. Lambda表達式對接口的要求
雖然說, lambda表達式可以在一定程度上簡化接口的實現。 但是, 並不是所有的接口都可以使用lambda表達式來簡潔實現的。
lambda表達式畢竟只是一個匿名方法。 當實現的接口中的方法過多或者多少的時候, lambda表達式都是不適用的。
lambda表達式,只能實現函數式接口。
22.1.4. 函數式接口
22.1.4.1. 基礎概念
如果說, 一個接口中, 要求實現類必須實現的抽象方法, 有且只有一個! 這樣的接口, 就是函數式接口。
// 這個接口中, 有且只有一個方法, 是實現類必須實現的, 因此是一個函數式接口
interface Test1 {
void test();
}
// 這個接口中, 實現類必須要實現的方法, 有兩個! 因此不是一個函數式接口
interface Test2 {
void test1();
void test2();
}
// 這個接口中, 實現類必須要實現的方法, 有零個! 因此不是一個函數式接口
interface Test3 {
}
// 這個接口中, 雖然沒有定義任何的方法, 但是可以從父接口中繼承到一個抽象方法的。 是一個函數式接口
interface Test4 extends Test1 {
}
// 這個接口, 雖然裏面定義了兩個方法, 但是defualt方法子類不是必須實現的。
// 因此, 實現類實現這個接口的時候, 必須實現的方法只有一個! 是一個函數式接口。
interface Test5 {
void test5();
default void test() {}
}
// 這個接口中的 toString 方法, 是Object類中定義的方法。
// 此時, 實現類在實現接口的時候, toString可以不重寫的! 因爲可以從父類Object中繼承到!
// 此時, 實現類在實現接口的時候, 有且只有一個方法是必須要重寫的。 是一個函數式接口!
interface Test6 {
void test6();
String toString();
}
思考題: 下面的兩個接口是不是函數式接口?
interface Test7 {
String toString();
}
interface Test8 {
void test();
default void test1() {}
static void test2() {}
String toString();
}
22.1.4.2. @FunctionalInterface
是一個註解, 用在接口之前, 判斷這個接口是否是一個函數式接口。 如果是函數式接口, 沒有任何問題。 如果不是函數式接口, 則會報錯。 功能類似於 @Override。
@FunctionalInterface
interface FunctionalInterfaceTest {
void test();
}
22.1.4.3. 系統內置的若干函數式接口
22.2. Lambda表達式的語法
22.2.1. Lambda表達式的基礎語法
lambda表達式, 其實本質來講, 就是一個匿名函數。 因此在寫lambda表達式的時候, 不需要關心方法名是什麼。
實際上, 我們在寫lambda表達式的時候, 也不需要關心返回值類型。
我們在寫lambda表達式的時候, 只需要關注兩部分內容即可: 參數列表 和 方法體
lambda表達式的基礎語法:
(參數) ->{
方法體
};
參數部分 : 方法的參數列表, 要求和實現的接口中的方法參數部分一致, 包括參數的數量和類型。
方法體部分 : 方法的實現部分, 如果接口中定義的方法有返回值, 則在實現的時候, 注意返回值的返回。
-> : 分隔參數部分和方法體部分。
/**
* @Description
*/
public class Syntax {
public static void main(String[] args) {
// 1. 無參、無返回值的方法實現
NoneReturnNoneParameter lambda1 = () -> {
System.out.println("無參、無返回值方法的實現");
};
lambda1.test();
// 2. 有參、無返回值的方法實現
NoneReturnSingleParameter lambda2 = (int a) -> {
System.out.println("一個參數、無返回值方法的實現: 參數是 " + a);
};
lambda2.test(10);
// 3. 多個參數、無返回值方法的實現
NoneReturnMutipleParameter lambda3 = (int a, int b) -> {
System.out.println("多個參數、無返回值方法的實現: 參數a是 " + a + ", 參數b是 " + b);
};
lambda3.test(10, 20);
// 4. 無參、有返回值的方法的實現
SingleReturnNoneParameter lambda4 = () -> {
System.out.println("無參、有返回值方法的實現");
return 666;
};
System.out.println(lambda4.test());
// 5. 一個參數、有返回值的方法實現
SingleReturnSingleParameter lambda5 = (int a) -> {
System.out.println("一個參數、有返回值的方法實現: 參數是 " + a);
return a * a;
};
System.out.println(lambda5.test(9));
// 6. 多個參數、有返回值的方法實現
SingleReturnMutipleParameter lambda6 = (int a, int b) -> {
System.out.println("多個參數、有返回值的方法實現: 參數a是 " + a + ", 參數b是 " + b);
return a * b;
};
System.out.println(lambda6.test(10, 20));
}
}
22.2.2. Lambda表達式的語法進階
在上述代碼中, 的確可以使用lambda表達式實現接口, 但是依然不夠簡潔, 有簡化的空間。
22.2.2.1. 參數部分的精簡
- 參數的類型
- 由於在接口的方法中,已經定義了每一個參數的類型是什麼。 而且在使用lambda表達式實現接口的時候, 必須要保證參數的數量和類型需要和接口中的方法保持一致。 因此, 此時lambda表達式中的參數的類型可以省略不寫。
- 注意事項:
- 如果需要省略參數的類型, 要保證: 要省略, 每一個參數的類型都必須省略不寫。 絕對不能出現, 有的參數類型省略了, 有的參數類型沒有省略。
// 多個參數、無返回值的方法實現
NoneReturnMutipleParameter lambda1 = (a, b) -> {
System.out.println("多個參數、無返回值方法的實現: 參數a是 " + a + ", 參數b是 " + b);
};
- 參數的小括號
- 如果方法的參數列表中的參數數量 有且只有一個 ,此時,參數列表的小括號是可以省略不寫的。
- 注意事項:
- 只有當參數的數量是一個的時候, 多了、少了都不能省略。
- 省略掉小括號的同時, 必須要省略參數的類型。
// 有參、無返回值的方法實現
NoneReturnSingleParameter lambda2 = a -> {
System.out.println("一個參數、無返回值方法的實現: 參數是 " + a);
};
22.2.2.2. 方法體部分的精簡
- 方法體大括號的精簡
- 當一個方法體中的邏輯, 有且只有一句的情況下, 大括號可以省略。
// 有參、無返回值的方法實現
NoneReturnSingleParameter lambda2 = a -> System.out.println("一個參數、無返回值方法的實現: 參數是 " + a);
- return的精簡
- 如果一個方法中唯一的一條語句是一個返回語句, 此時在省略掉大括號的同時, 也必須省略掉return。
SingleReturnMutipleParameter lambda3 = (a, b) -> a + b;
22.3. 函數引用
lambda表達式是爲了簡化接口的實現的。 在lambda表達式中, 不應該出現比較複雜的邏輯。 如果在lambda表達式中出現了過於複雜的邏輯, 會對程序的可讀性造成非常大的影響。 如果在lambda表達式中需要處理的邏輯比較複雜, 一般情況會單獨的寫一個方法。 在lambda表達式中直接引用這個方法即可。
或者, 在有些情況下, 我們需要在lambda表達式中實現的邏輯, 在另外一個地方已經寫好了。 此時我們就不需要再單獨寫一遍, 只需要直接引用這個已經存在的方法即可。
函數引用: 引用一個已經存在的方法, 使其替代lambda表達式完成接口的實現。
22.3.1. 靜態方法的引用
- 語法:
- 類::靜態方法
- 注意事項:
- 在引用的方法後面, 不要添加小括號。
- 引用的這個方法, 參數(數量、類型) 和 返回值, 必須要跟接口中定義的一致。
- 示例:
/**
* @Description
*/
public class Syntax1 {
// 靜態方法的引用
public static void main(String[] args) {
// 實現一個多個參數的、一個返回值的接口
// 對一個靜態方法的引用
// 類::靜態方法
SingleReturnMutipleParameter lambda1 = Calculator::calculate;
System.out.println(lambda1.test(10, 20));
}
private static class Calculator {
public static int calculate(int a, int b) {
// 稍微複雜的邏輯:計算a和b的差值的絕對值
if (a > b) {
return a - b;
}
return b - a;
}
}
}
22.3.2. 非靜態方法的引用
- 語法:
- 對象::非靜態方法
- 注意事項:
- 在引用的方法後面, 不要添加小括號。
- 引用的這個方法, 參數(數量、類型) 和 返回值, 必須要跟接口中定義的一致。
- 示例:
/**
* @Description
*/
public class Syntax2 {
public static void main(String[] args) {
// 對非靜態方法的引用,需要使用對象來完成
SingleReturnMutipleParameter lambda = new Calculator()::calculate;
System.out.println(lambda.test(10, 30));
}
private static class Calculator {
public int calculate(int a, int b) {
return a > b ? a - b : b - a;
}
}
}
22.3.3. 構造方法的引用
- 使用場景
如果某一個函數式接口中定義的方法, 僅僅是爲了得到一個類的對象。 此時我們就可以使用構造方法的引用, 簡化這個方法的實現。
- 語法
類名::new
- 注意事項
可以通過接口中的方法的參數, 區分引用不同的構造方法。
- 示例
/**
* @Description
*/
public class Syntax3 {
private static class Person {
String name;
int age;
public Person() {
System.out.println("一個Person對象被實例化了");
}
public Person(String name, int age) {
System.out.println("一個Person對象被有參的實例化了");
this.name = name;
this.age = age;
}
}
@FunctionalInterface
private interface GetPerson {
// 僅僅是希望獲取到一個Person對象作爲返回值
Person test();
}
private interface GetPersonWithParameter {
Person test(String name, int age);
}
public static void main(String[] args) {
// lambda表達式實現接口
GetPerson lambda = Person::new; // 引用到Person類中的無參構造方法,獲取到一個Person對象
Person person = lambda.test();
GetPersonWithParameter lambda2 = Person::new; // 引用到Person類中的有參構造方法,獲取到一個Person對象
lambda2.test("xiaoming", 1);
}
}
22.3.4. 對象方法的特殊引用
如果在使用lambda表達式,實現某些接口的時候。 lambda表達式中包含了某一個對象, 此時方法體中, 直接使用這個對象調用它的某一個方法就可以完成整體的邏輯。 其他的參數, 可以作爲調用方法的參數。 此時, 可以對這種實現進行簡化。
/**
* @Description
*/
public class Syntax {
public static void main(String[] args) {
// 如果對於這個方法的實現邏輯,是爲了獲取到對象的名字
GetField field = person -> person.getName();
// 對於對象方法的特殊引用
GetField field = Person::getName;
// 如果對於這個方法的實現邏輯,是爲了給對象的某些屬性進行賦值
SetField lambda = (person, name) -> person.setName(name);
SetField lambda = Person::setName;
// 如果對於這個方法的實現邏輯,正好是參數對象的某一個方法
ShowTest lambda2 = person -> person.show();
ShowTest lambda2 = Person::show;
}
}
interface ShowTest {
void test(Person person);
}
interface SetField {
void set(Person person, String name);
}
interface GetField {
String get(Person person);
}
class Person {
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void show() {
}
}
22.4. Lambda表達式需要注意的問題
這裏類似於局部內部類、匿名內部類,依然存在閉包的問題。
如果在lambda表達式中,使用到了局部變量,那麼這個局部變量會被隱式的聲明爲 final。 是一個常量, 不能修改值。
22.5. Lambda表達式的實例
22.5.1. 線程的實例化
Thread thread = new Thread(() -> {
// 線程中的處理
});
22.5.2. 集合的常見方法
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "千鋒", "大數據", "好程序員", "嚴選", "高薪");
// 按照條件進行刪除
list.removeIf(ele -> ele.endsWith(".m"));
// 批量替換
list.replaceAll(ele -> ele.concat("!"));
// 自定義排序
list.sort((e1, e2) -> e2.compareTo(e1));
// 遍歷
list.forEach(System.out::println);
22.5.3. 集合的流式編程
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "千鋒", "大數據", "好程序員", "嚴選", "高薪");
list.parallelStream().filter(ele -> ele.length() > 2).forEach(System.out::println);