Java Lambda詳解
Lambda表達式是JDK 8開始後的一種新語法形式。
作用:簡化匿名內部類的程式碼寫法
簡化格式
(匿名內部類被重寫方法的形參列表) -> {
重寫方法
}
Lambda表達式只能簡化函數式介面的匿名內部類的寫法形式
什麼是函數式介面?
- 首先必須是介面、其次介面中有且僅有一個抽象方法的形式
- 通常會在介面上加上一個@FunctionalInterface註解,標記該介面必須是滿足函數式介面
如何使用Lambda?
我們將根據下面三個問題來幫助大家理解和使用Lambda
背景:我們自定義了一個man的類,創建了一個man的List。
class man {
public int age;
public char sex;
public double socre;
public man(int age, char sex, double score) {
this.age = age;
this.sex = sex;
this.score = score;
}
}
問題一
現需要對這個list根據人的年齡進行排序
要實現排序的功能,可以直接調用List對象自帶的sort方法完成,但是需要man先實現Comparator的介面並重寫compare方法,編譯器才能比較兩個不同man的大小。但是要更改原始類的程式碼,會比較麻煩,如果以後要對人的分數進行排序,那就又要更改的類的源碼,這樣操作很不方便。
sort(Comparator<? super E> c)
方法可以直接傳入一個Comparator對象,我們可以直接改寫compare方法就可以實現比較。
第一種寫法
public class lambdaTry {
public static void main(String[] args) {
List<man> humans = new ArrayList<>();
humans.add(new man(19, 'g', 98.0));
humans.add(new man(18, 'b', 95.0));
humans.add(new man(20, 'b', 96.0));
humans.add(new man(17, 'g', 97.0));
humans.sort(new Comparator<man>() {
@Override
public int compare(man o1, man o2) {
return o1.age - o2.age;
}
});
}
}
第二種寫法
Lambda
我們知道Lambda是用來簡化函數式介面的匿名內部類,且Comparator滿足函數式介面的兩個條件:
- 首先必須是介面、其次介面中有且僅有一個抽象方法的形式
- @FunctionalInterface註解
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
...
}
因此我們可以對上述的源碼進行改寫成Lambda格式
public class lambdaTry {
public static void main(String[] args) {
List<man> humans = new ArrayList<>();
humans.add(new man(19, 'g', 98.0));
humans.add(new man(18, 'b', 95.0));
humans.add(new man(20, 'b', 96.0));
humans.add(new man(17, 'g', 97.0));
humans.sort((man o1, man o2) -> {
return o1.age - o2.age;
});
}
}
改寫過後程式碼簡潔了很多。但是還可以繼續簡寫。
Lambda表達式的省略寫法
- 參數類型可以不寫
- 如果只有一個參數,參數類型可以省略,同時()也可以省略
- 如果Lambda表達式的方法塊中程式碼只有一行,可以省略大括弧,同時省略分號。
- 在條件三的基礎上,如果這行程式碼是return語句,必須省略return。
第三種寫法
Lambda簡寫
可以看到,此表達式滿足省略寫法的條件,可以繼續簡寫成如下格式。只需要一行語句就能完成
public class lambdaTry {
public static void main(String[] args) {
List<man> humans = new ArrayList<>();
humans.add(new man(19, 'g', 98.0));
humans.add(new man(18, 'b', 95.0));
humans.add(new man(20, 'b', 96.0));
humans.add(new man(17, 'g', 97.0));
humans.sort((o1, o2) -> o1.age - o2.age);
}
}
問題二
將List轉換為數組
我們知道List介面有一個方法toArray方法可以實現將其轉換為數組。
JDK11之後,提供了這樣的一個方法,提供了一個函數式介面來讓我們轉換
default <T> T[] toArray(IntFunction<T[]> generator) {
return toArray(generator.apply(0));
}
IntFunction函數式介面是從JDK8之後實現的,內部只有一個apply抽象方法,是一個標準的函數式介面
@FunctionalInterface
public interface IntFunction<R> {
R apply(int value);
}
我們可以直接用lambda,完成數組的轉換
public class lambdaTry {
public static void main(String[] args) {
List<man> humans = new ArrayList<>();
humans.add(new man(19, 'g', 98.0));
humans.add(new man(18, 'b', 95.0));
humans.add(new man(20, 'b', 96.0));
humans.add(new man(17, 'g', 97.0));
// 原本寫法
// man[] mans = humans.toArray(new IntFunction<man[]>() {
// @Override
// public man[] apply(int value) {
// return new man[value];
// }
// });
// lambda寫法
man[] mans = humans.toArray(value -> new man[value]);
// 實際上用不上這樣的寫法,只是為了舉例說明
// man[] mans = humans.toArray(new man[0]);
// man[] mans = humans.toArray(man[]::new);
// 上面兩種寫法都可以,傳值進去的size為0不影響實際的轉換,具體可以看ArrayList的toArray重寫方法
}
}
問題三
輸出年齡大於18的男同學的成績
可以用forEach方法快捷實現,forEach方法來自於Iterable介面
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
再看Consumer介面,也是一個函數式介面
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
...
}
具體實現
public class lambdaTry {
public static void main(String[] args) {
List<man> humans = new ArrayList<>();
humans.add(new man(19, 'g', 98.0));
humans.add(new man(18, 'b', 95.0));
humans.add(new man(20, 'b', 96.0));
humans.add(new man(17, 'g', 97.0));
// humans.forEach(new Consumer<>() {
// @Override
// public void accept(man man) {
// if (man.age >= 18 && man.sex == 'g') {
// System.out.println(man.score);
// }
// }
// });
humans.forEach(man -> {
if (man.age >= 18 && man.sex == 'g') {
System.out.println(man.score);
}
});
}
}
有時Lambda還可以繼續簡寫成方法引用(method reference)
方法引用
方法引用通過方法的名字來指向一個方法。
方法引用可以使語言的構造更緊湊簡潔,減少冗餘程式碼。
方法引用使用一對冒號 ::
主要分為四種:
-
構造器引用
Class::new
man[] mans = humans.toArray(man[]::new);
-
靜態方法引用
Class::static_method
列印每個man(需要在man內重寫toString)
humans.forEach(System.out::println)
-
特定類的任意對象的方法引用
Class::method
-
特定對象的方法引用
instance::method