Lambda表達式
Lambda表達式是Java8推出的非常強大的特性之一。藉助它,我們對一些接口的簡單實現不再需要寫那麼多繁瑣,多餘的代碼,只需寫一些關鍵性的代碼,即簡潔又優雅,裝X與無形之中。但是正因如此,可讀性也就不要奢求了。
使用要求:待實現的接口中只能有一個需要被實現的方法,不包括default修飾的方法。而這種接口,就稱之爲函數式接口,通常用@FunctionalInterface
註解修飾。可參考另一篇博文函數式接口
基礎語法
語法形如 () -> {}
->
是Lambda運算符,->
的左邊部分()
是接口對應方法的入參列表,右邊{}
是接口對應方法的實現體
- 接口中待實現的方法:無入參,無返回值(以Runnable接口爲例)
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
@Test
public void test1() {
Runnable runnable = () -> {System.out.println("hello world!");};
runnable.run();
}
//對於實現體中只包含一句話的情況,可以將{}省去不寫
Runnable runnable = () -> System.out.println("hello world!");
-
接口中待實現的方法:有一個入參,無返回值(以Consumer接口爲例,該接口爲四大內置接口之一)
@FunctionalInterface public interface Consumer<T> { void accept(T t); ... } //測試:將字符串作爲入參,做打印處理 @Test public void test1() { Consumer<String> consumer = (str) -> System.out.println(str); consumer.accept("hello consumer!"); } //可以看出入參的參數類型是可以省略不寫的(寫上也沒錯) //另外,若參數列表中只有一個參數,則()可以省略不寫,習慣上還是寫上 Consumer<String> consumer = str -> System.out.println(str);
-
接口中待實現的方法:有兩個及以上的參數,有返回值,有多條語句(以Comparator接口爲例)
@FunctionalInterface public interface Comparator<T> { int compare(T o1, T o2); } //測試:將一串數字按升序排序 @Test public void test3() { Comparator<Integer> comparator = (x, y) -> { System.out.println("hello lambda!"); return Integer.compare(x, y); }; List<Integer> list = Arrays.asList(1, 3, -5, 2); Collections.sort(list, comparator); list.forEach(System.out::println);//一個循環打印的語句 }
-
接口中待實現的方法:參數不定,有返回值,但是實現體中只有一條語句(仍以Comparator接口爲例)
//測試:將一串數字按升序排序 @Test public void test3() { Comparator<Integer> comparator = (x, y) -> Integer.compare(x, y); List<Integer> list = Arrays.asList(1, 3, -5, 2); Collections.sort(list, comparator); list.forEach(System.out::println);//一個循環打印的語句 } //可見當只有一條語句時,return和{}都可以省略
簡單總結:
-
左右遇一括省:"->"左邊或右邊均只有一個參數或一條語句時,()或 {} 可以省略
-
右側只有一條語句,且有返回值時,return可省略
-
參數類型可以省略
方法引用
- 若Lambda表達式右側的實現體中已經有方法實現了,那我們就可以使用“方法引用”。
引用條件 :被引用方法的參數列表與返回值類型需要與接口中待實現的方法的參數列表與返回值類型保持一致。
三種語法格式:
- 對象::實例方法名
- 類::靜態方法名
- 類::實例方法名
示例如下:
-
對象::實例名(以Consumer接口和PrintStream中的println方法爲例)
@Test public void test4() { PrintStream ps = System.out; Consumer<String> consumer = ps::println; //等同於 Consumer<String> consumer = System.out::println; //等同於 Consumer<String> consumer = (str) -> System.out.println(str); consumer.accept("hello lambda!"); } //因爲Consumer中void accept(T t)方法 //可以對應於PrintStream中的void println(T t)方法 //所以可以使用方法引用,且入參列表與方法的()符號可以省略
-
類::靜態方法名(以Comparator接口和Integer類中靜態方法compare爲例)
@Test public void test5() { Comparator<Integer> comparator = Integer::compare; //等同於Comparator<Integer> comparator = (x, y) -> Integer.compare(x, y); List<Integer> list = Arrays.asList(1, 3, -5, 2); Collections.sort(list, comparator); list.forEach(System.out::println); } //Comparator中int compare(T o1, T o2) 對應於 Integer中static int compare(int x, int y)
-
類::實例方法名(以BiPredicate接口和String類中實例方法equals爲例)
@Test public void test6() { BiPredicate<String, String> bp = String::equals; //等同於BiPredicate<String, String> bp = (x, y) -> x.equals(y); String x = "hello"; String y = "hello"; System.out.println(bp.test(x, y)); } //使用條件:當參數列表(x, y)中x爲該方法的調用者,y爲該方法的入參時,才能使用。
構造器引用
- 同方法引用類似,只是書寫格式不同
- 格式爲:ClassName::new
示例
現假定有一個People類,屬性如下(有三個構造方法):
public class People {
private String name;
private Integer age;
private Integer salary;
public People() {}
public People(String name) {
this.name = name;
}
public People(String name, Integer age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "People [name=" + name + ", age=" + age + ", salary=" + salary + "]";
}
}
利用Supplier接口生成一個People對象
@Test
public void test7() {
Supplier<People> supplier = People::new;
//Supplier<People> supplier = () -> new People();
People people = supplier.get();
System.out.println(people);
}
//可以看到會打印出屬性值均爲null的People對象
People [name=null, age=null, salary=null]
疑問:一個類中有可能有很多個構造方法,那到底會調用哪一個呢?(先看下面的示例)
@Test
public void test8() {
Function<String, People> function = People::new;
// Function<String, People> function = (name) -> new People(name);
People people = function.apply("Tony");
System.out.println(people);
}
//運行結果:
People [name=Tony, age=null, salary=null]
可以看到這一次利用Function接口的apply方法生成的people對象中,name屬性是有值的,這說明在生成對象時調用的構造器是有參構造器public People(String name){}。
同樣是People::new
卻調用了不同的構造器,由此可以明白構造器引用具體選擇哪個構造方法取決於接口中待實現方法的入參。
上述所有示例中多處用到了Java8內置的四大核心函數式接口,可參考函數式接口