1. Lambda表達式簡介
1.1 什麼是Lambda?
Lambda 表達式,也可稱爲閉包,可以看作爲匿名內部類。它是推動 Java 8 發佈的最重要新特性。
Lambda 允許把函數作爲一個方法的參數(函數作爲參數傳遞進方法中)。
使用 Lambda 表達式可以使代碼變的更加簡潔緊湊。
1.2 爲什麼要使用Lambda表達式?
使用Lambda表達式可以對一個接口進行非常簡單的實現,接口的常規實現就是,實現類或匿名內部類,下面通過一個小的示例,對比三種實現方式
public class Test {
public static void main(String[] args) {
//1.使用接口實現類
Comparator comparator = new MyComparator();
comparator.compare(a,b);
//2.使用匿名內部類
Comparator comparator1 = new Comparator() {
@Override
public int compare(int a, int b) {
return a - b;
}
};
//3.使用Lambda表達式來實現接口
Comparator comparator2 = (a,b) -> a - b;
}
}
class MyComparator implements Comparator {
@Override
public int compare(int a, int b) {
return a - b;
}
}
interface Comparator{
int compare(int a, int b);
}
通過對比,就可以看出Lambda表達式實現接口的簡潔和可讀性了。
1.3 Lambda對接口的要求
試想一下,如果一個接口中有多個方法,會是怎樣的情形。如果方法參數列表相同,Lambda表達式無法分辨或者說是識別實現哪一個方法。雖然可以使用Lambda表達式對某些接口進行簡單的實現,但是並不是所有的接口都能用Lambda表達式來實現。要求接口中定義的要實現的方法只有一個。
- 在Java8對接口加了一個新特性:default,會默認實現,如果接口實現類不重寫該方法,就會執行執行方法的默認邏輯。接口中default修飾的方法,對Lambda表達式無影響
- @FuncionalInterface修飾函數式接口的,被修飾的接口中只能定義一個抽象方法,default修飾的方法除外。所以用Lambda表達式實現的接口,儘量用該註解修飾。
2. Lambda表達式基礎語法
2.1接口準備
/**
* 無返回值,無參數
*/
@FunctionalInterface
public interface LambdaNoneReturnNoneParameter {
void test();
}
/**
* 無返回值,單個參數
*/
@FunctionalInterface
public interface LambdaNoneReturnSingleParameter {
void test(int n);
}
/**
* 無返回值,多個參數
*/
@FunctionalInterface
public interface LambdaNoneReturnMultipleParameter {
void test(int a, int b);
}
/**
* 有返回值,無參數
*/
@FunctionalInterface
public interface LambdaWithReturnNoneParameter {
int test();
}
/**
* 有返回值,單個參數
*/
@FunctionalInterface
public interface LambdaWithReturnSingleParameter {
int test(int a);
}
/**
* 有返回值,多個參數
*/
@FunctionalInterface
public interface LambdaWithReturnMulpitleParameter {
int test(int a, int b);
}
2.2 通過對上面接口的使用來了解Lambda語法
2.2.1 Lambda表達式基礎語法
Lambda是一個匿名函數,包含參數列表,方法體,(參數名:參數類型,參數名:參數類型) -> {方法體}
-
(): 用來描述參數列表
-
{}: 用來描述方法體
-
->: Lambda運算符
無返回值、無參數,接口的實現
LambdaNoneReturnNoneParameter lambda1 = () -> {
System.out.println("No Parameter, No Return.");
};
lambda1.test();//No Parameter, No Return.
無返回值、單個參數,接口的實現
LambdaNoneReturnSingleParameter lambda2 = (int a) -> {
System.out.println(a);
};
lambda2.test(2);//2
無返回值、多個參數,接口的實現
LambdaNoneReturnMultipleParameter lambda3 = (int a, int b) -> {
System.out.println(a + b);
};
lambda3.test(1, 99);//100
有返回值、無參數,接口的實現
LambdaWithReturnNoneParameter lambda4 = () -> {
System.out.println("with return, no parameter.");
return 100;
};
int result = lambda4.test();
System.out.println(result);//100
有返回值、單個參數,接口的實現
LambdaWithReturnSingleParameter lambda5 = (int a) -> {
return a * 2;
};
int result2 = lambda5.test(168);
System.out.println(result2);//336
有返回值、多個參數,接口的實現
LambdaWithReturnMulpitleParameter lambda6 = (int a, int b) -> {
return a * b;
};
int result3 = lambda6.test(9, 10);
System.out.println(result3);//90
3.Lambda表達式語法精簡
3.1參數類型精簡
由於在接口的抽象方法中,已經定義了參數的數量和類型,所以在Lambda表達式中,參數類型可以省略
備註:如果要省略參數類型,則每一個參數的類型都要省略,千萬不要只省略一部分參數。
LambdaNoneReturnMultipleParameter lambda1 = (a, b) -> {
System.out.println(a + b);
};
lambda1.test(1,2);//3
3.2參數列表小括號精簡
如果參數列表中,參數的數量只有一個,此時小括號可以省略
LambdaNoneReturnSingleParameter lambda2 = a -> {
System.out.println(a);
};
lambda2.test(2);//2
3.3 方法體大括號精簡
如果方法體中只有一條語句,此時大括號可以省略
LambdaNoneReturnSingleParameter lambda3 = a -> System.out.println(a);
lambda2.test(3);//3
3.4 return精簡
如果方法體中只有一條語句,並且這條語句是返回語句,則在省略大括號的同時,也必須省略return
LambdaWithReturnMulpitleParameter lambda4 = () -> 4;
int result = lambda4.test();
System.out.println(result);//4
LambdaWithReturnMulpitleParameter lambda5 = (a, b) -> a + b;
int result2 = lambda5.test(1, 2);
System.out.println(result);//3
4.Lambda表達式語法進階
4.1方法引用
如果有多處需要實現同一接口執行同樣的操作,每次都要重複Lambda表達式中方法體邏輯,可以採取方法引用來解決這個問題,將Lambda表達式方法體中邏輯抽取到某個類的靜態方法中,即使多處需要實現接口,只需通過Lambda表達式進行方法引用即可。引用某個類的方法類名::方法,類似通過類名調用方法,方法需要是靜態的。如果是需要引用非靜態方法,還需要先實例化對象,然後調用方法,意義不太。
public class Test3 {
public static void main(String[] args) {
//方法引用:
//可以快速的將一個Lambda表達式的實現指向一個已經實現的方法
//方法的隸屬者:: 方法名
//注意:
//1.參數數量和類型必須要和接口中定義的方法保持一致
//2.返回值的類型也必須要和接口中定義的方法保持一致
LambdaWithReturnSingleParameter lambda1 = a -> change(a);
int result = lambda1.test(3);
System.out.println(result);
//方法引用:引用了change方法的實現
LambdaWithReturnSingleParameter lambda2 = Test3::change;
int result2 = lambda2.test(5);
System.out.println(result2);
}
private static int change(int a) {
return a * 2;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
4.2構造方法引用
先定義一個Person類,它的無參構造和帶參如下:
public class Person {
private String name;
private int age;
public Person() {
System.out.println("Person類的無參構造執行了.");
}
public Person(String name, int age) {
this.name = name;
this.age = age;
System.out.println("Person類的有參構造執行了.");
}
}
常規Lambda表達式實現:只有一條語句{}可省略
構造引用語法如下:
類名:: new
public class Test4 {
public static void main(String[] args) {
//常規Lambda表達式
PersonCreator creator1 = () -> new Person();
Person person1 = creator1.getPerson();//Person類的無參構造執行了.
//無參構造引用
PersonCreator creator2 = Person::new;
Person person2 = creator2.getPerson();//Person類的無參構造執行了.
//帶參構造引用
PersonCreatorWithParameter creator3 = Person::new;
creator3.getPerson("小明", 12);//Person類的有參構造執行了.
}
}
//需求:
interface PersonCreator{
Person getPerson();
}
interface PersonCreatorWithParameter{
Person getPerson(String name, int age);
}
5.Lambda表達式之綜合案例
5.1 集合排序Comparator
public class Exercise1 {
@RequiresApi(api = Build.VERSION_CODES.N)
public static void main(String[] args) {
//ArrayList
//需求:已知在一個ArrayList中有若干個Person對象,將這些Person對象按照年齡進行降序排序
ArrayList<Person> list = new ArrayList<>();
list.add(new Person("xiaoming", 10));
list.add(new Person("lilei", 11));
list.add(new Person("hanmeimei", 12));
list.add(new Person("lily", 9));
list.add(new Person("lucy", 9));
list.add(new Person("polly", 3));
list.add(new Person("uncle wang", 40));
//排序
list.sort((a1, a2) -> a2.age - a1.age);
System.out.println(list);
//[Person{name='uncle wang', age=40}, Person{name='hanmeimei', age=12}, Person{name='lilei', age=11}, Person{name='xiaoming', age=10}, Person{name='lily', age=9}, Person{name='lucy', age=9}, Person{name='polly', age=3}]
}
}
再看Lambda表達式實現TreeSet集合排序,通過TreeSet構造,我們知道TreeSet自帶排序功能
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
通過打印日誌,發現age爲9的lucy沒有了,這是因爲TreeSet的去重功能。與Lambda表達式無關。若想保留age爲9的重複元素,可進行如下操作。
public class Exercise2 {
public static void main(String[] args) {
//需求:已知在一個ArrayList中有若干個Person對象,將這些Person對象按照年齡進行降序排序
//會去重age爲9的一個元素
//TreeSet<Person> list = new TreeSet<>((a1, a2) -> a2.age - a1.age);
//[Person{name='uncle wang', age=40}, Person{name='hanmeimei', age=12}, Person{name='lilei', age=11}, Person{name='xiaoming', age=10}, Person{name='lily', age=9}, Person{name='polly', age=3}]
//保留重複元素
TreeSet<Person> list = new TreeSet<>((a1, a2) -> {
if (a1.age >= a2.age) {
return -1;
} else {
return 1;
}
});
//[Person{name='uncle wang', age=40}, Person{name='hanmeimei', age=12}, Person{name='lilei', age=11}, Person{name='xiaoming', age=10}, Person{name='lucy', age=9}, Person{name='lily', age=9}, Person{name='polly', age=3}]
list.add(new Person("xiaoming", 10));
list.add(new Person("lilei", 11));
list.add(new Person("hanmeimei", 12));
list.add(new Person("lily", 9));
list.add(new Person("lucy", 9));
list.add(new Person("polly", 3));
list.add(new Person("uncle wang", 40));
System.out.println(list);
}
}
5.2集合遍歷 forEach()
public class Exercise3 {
@RequiresApi(api = Build.VERSION_CODES.N)
public static void main(String[] args) {
//集合遍歷
//需求:已知在一個ArrayList中有若干個Person對象,將這些Person對象按照年齡進行降序排序
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0);
//將集合中的每一個元素都帶入到方法accept中
list.forEach(System.out::println);
//輸出集合中所有的偶數
list.forEach(element -> {
if (element % 2 == 0) {
System.out.println(element);
}
});
}
}
5.3 刪除集合中滿足條件的元素removeIf()
public class Exercise4 {
@RequiresApi(api = Build.VERSION_CODES.N)
public static void main(String[] args) {
//需求:刪除滿足條件的元素
ArrayList<Person> list = new ArrayList<>();
list.add(new Person("xiaoming", 10));
list.add(new Person("lilei", 11));
list.add(new Person("hanmeimei", 12));
list.add(new Person("lily", 9));
list.add(new Person("lucy", 9));
list.add(new Person("polly", 3));
list.add(new Person("uncle wang", 40));
//刪除集合中年齡大於10的元素
//常規實現
/*ListIterator<Person> iterator = list.listIterator();
while (iterator.hasNext()) {
Person element = iterator.next();
if (element.age > 10) {
iterator.remove();
}
}*/
//Lambda表達式實現
//將集合中每一個元素帶入到test方法中,如果返回true,則刪除該元素
list.removeIf(element -> element.age > 10);
System.out.println(list);
}
}
5.4 線程實例化
public class Exercise5 {
public static void main(String[] args) {
//需求:開啓一個線程,進行數字輸出
//常規操作
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
});
thread1.start();
//Lambda實現
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
});
thread2.start();
}
}
6.閉包問題
常規認知在java中,局部變量的生命週期在局部方法調用完畢時結束。也就是說在getNumber方法調用結束後,num應該銷燬了。但是在getNumber方法中,又通過Lambda閉包提升了num的生命週期,這樣就可以在外部獲取方法中的局部變量了。
public class ClosureDemo {
@RequiresApi(api = Build.VERSION_CODES.N)
public static void main(String[] args) {
int n = getNumber().get();
System.out.println(n);//10
}
//Supplier<T> 無參、返回值爲T類型的接口
private static Supplier<Integer> getNumber() {
int num = 10;
return () -> {
return num;
};
}
}
在Lambda閉包中引用的變量,必須是常量,也就是說會默認添加final修飾符。
public class ClosureDemo2 {
@RequiresApi(api = Build.VERSION_CODES.N)
public static void main(String[] args) {
//Consumer<T> 參數類型爲T 無返回值的接口
int a = 9;
//Lambda表達式使用局部變量時,默認添加了final修飾符
Consumer<Integer> consumer = element -> System.out.println(a);
// a++; //此時a 已經是final了,不可改變
//(閉包)Lambda表達式中使用的局部變量應該是final,不可以改變
// Consumer<Integer> consumer2 = element -> System.out.println(a++);
consumer.accept(1);
}
}
添加final修飾符。
```java
public class ClosureDemo2 {
@RequiresApi(api = Build.VERSION_CODES.N)
public static void main(String[] args) {
//Consumer<T> 參數類型爲T 無返回值的接口
int a = 9;
//Lambda表達式使用局部變量時,默認添加了final修飾符
Consumer<Integer> consumer = element -> System.out.println(a);
// a++; //此時a 已經是final了,不可改變
//(閉包)Lambda表達式中使用的局部變量應該是final,不可以改變
// Consumer<Integer> consumer2 = element -> System.out.println(a++);
consumer.accept(1);
}
}