Lambda表達式是Java 8一個非常重要的新特性。它像方法一樣,利用很簡單的語法來定義參數列表和方法體。目前Lambda表達式已經成為高級編程語言的標配,像Python,Swift等都已經支持Lambda表達式。
在Java 8的實現中,Lambda表達式其本質只是一個“語法糖”,經過編譯器推斷和處理,將其轉換包裝為常規的Java代碼,因此就像題目所寫的那樣,可以讓你的代碼更為簡潔。
Lambda表達式的基本語法:(parameters) -> expression 或 (parameters) -> { statements; }
Lambda表達式並不是一個方法,它可以用來定義了一個代碼塊,形式上很像是Java的匿名內部類。Lambda表達式通常會賦值給一個函數式接口,函數式接口是指只有一個抽象方法的接口。Lambda表達式可以通過上下文環境來推斷變量類型, 因此在使用時盡量不人為明確的指定變量類型。
舉例來看,假設我們有一個List<String>類型的列表list,如果要遍歷並打印列表內容,Java 7以前的代碼如下:
1 for (String s : list) { 2 System.out.println(s); 3 }
Java 8來實現的話:
1 list.forEach((s) -> System.out.println(s));
或者
1 list.forEach(System.out::println);
再看一個例子,假設我們要對list進行排序,Java 7的代碼如下:
1 Collections.sort(list, new Comparator<String>() { 2 @Override 3 public int compare(String p1, String p2) { 4 return p1.compareTo(p2); 5 } 6 });
Java 8來實現的話:
1 Collections.sort(list, (String p1, String p2) -> p1.compareTo(p2));
需要注意的是,Lambda表達式可以做參數類型推斷,這裡我們可以充分利用這一點,p1和p2參數前面的String是不需要的,因此可以簡化一步如下:
1 Collections.sort(list, (p1,p2) -> p1.compareTo(p2));
更進一步:
1 list.sort((p1,p2) -> p1.compareTo(p2));
是不是簡潔了很多:)
Lambda表達式也可以用來代替匿名類。例如我們要實現Runnable接口,Java 7的代碼如下:
1 new Thread(new Runnable() { 2 @Override 3 public void run() { 4 System.out.println("Hello world !"); 5 } 6 }).start();
Java 8來實現的話:
1 new Thread(() -> System.out.println("Hello world !")).start();
用Lambda表達式來實現Runnable,將五行代碼轉換成一行語句。
合理使用Lambda表達式,不僅能簡化幾行代碼,還能做到合理的代碼抽象。當我們在實現的兩個很大的方法時,如果大部分的代碼都是相同的,只有一小點代碼不一樣時,我們可以通過將Lambda表達式作為參數傳入,以達到不同表意的目的。
前面提到的函數式接口(Functional Interfaces),它表示只有一個抽象方法的接口,可以用來指向Lambda表達式。例如:
1 Consumer c = (s) -> System.out.println(s);
Java 8在java.util.function包中實現了新的幾個:
Function<T, R>:接受一個參數T,返回結果R
Predicate<T>:接受一個參數T,返回boolean
Supplier<T>:不接受任何參數,返回結果T
Consumer<T>:接受一個參數T,不返回結果
UnaryOperator<T>:繼承自Function<T, T>,接受一個參數T,返回相同類型T的結果
BiFunction<T, U, R>:接受兩個參數T和U,返回結果R
BinaryOperator<T>:繼承自BiFunction<T, T, T>,接受兩個相同類型T的參數,返回相同類型T的結果
……
另外,我們最為熟悉的函數式接口還有:
Runnable:實際上是不接受任何參數,也不返回結果
Comparable<T>:實際上是接受兩個相同類型T的參數,返回int
Callable<V>:不接受任何參數,返回結果V
通常我們應該盡量使用標准的函數式接口,如果我們要自定義的話,可以使用@FunctionalInterface注解,例如:
1 @FunctionalInterface 2 public interface funcInterface { 3 public abstract B op(A a); 4 }
在將函數式接口作為參數時,需要注意盡量避免方法重載。由於Lambda表達式根據所在環境的目標類型來決定Lambda表達式的類型(也就是Target Typing), 因此方法重載有時會導致編譯器犯暈。我們可以使用不同的方法名來解決這個問題。
在這裡,我們還需要澄清幾點:
Lambda表達式並不是函數式接口。它能賦值給函數式接口,是因為編譯器將它包裝成了對應的函數式接口;
更進一步,Lambda表達式也不是匿名類:
它並沒有定義新的作用域,外面定義的局部變量在Lambda表達式內部是可見的;
它不能改變外部變量的值,只能讀取final或者effectively final的變量;
它不能前向讀取外部變量,也就是只有在外部變量申明之後才能讀取,而在匿名內部類是可以的;
Java 8 還增強了對集合數據的批量操作Stream,通常會和Lambda表達式一起使用。Lambda表達式和 Stream 可以說是Java語言從添加泛型(Generics)和注解(annotation)以來最大的變化了。下一篇文章將重點介紹Stream。