lambda 表達式是 java 8 中最引人注目的新特性,可是它自身的概念並很差理解,爲了更好的掌握它,咱們必須先了解一下函數式編程。網上關於這方面的介紹有不少,可是大多說的含糊不清。這裏提供一篇我認爲說的最明白的文章,是一篇譯文,若是有疑問還能夠直接對比原文。 傻瓜函數式編程java
一個lambda 是一段帶有參數的代碼塊,它能夠被傳遞,所以,它能夠執行一次或屢次。先從一個簡單、經典的比較器例子入手,來感覺一下 lambda 表達式git
List<String> list = Arrays.asList("aaa", "bbb", "ccc"); list.sort(new Comparator<String>() { @Override public int compare(String o1, String o2) { return Integer.compare(o1.length(), o2.length()); } });
它改寫成 lambda 表達式的樣子是:github
List<String> list = Arrays.asList("aaa", "bbb", "ccc"); list.sort((o1, o2) -> Integer.compare(o1.length(), o2.length()));
仔細對比一下上面的例子的兩段代碼,咱們思考幾個問題:編程
簡單抽象一下,基本語法的格式是這個樣子:安全
(Type1 param1, Type2 param2, ..., TypeN paramN) -> { statment1; statment2; //............. return statmentM; }
可是上面的例子 (o1, o2) -> Integer.compare(o1.length(), o2.length())) 顯然和語法格式不一致,咱們繼續以這段代碼爲例,詳細說一下 lambda 表達式的語法。 o1, o2 是什麼呢?它們都是字符串,Java 是強類型語言,咱們必須爲每一個變量或參數指明類型,一個正經 lambda 的寫法以下: 若是一個表達式的參數類型是能夠被推導的,那麼能夠忽略它們的類型ide
(o1, o2) -> { return Integer.compare(o1.length(), o2.length()); }
若是一個表達式的返回類型是能夠被推導的,那麼它將會從上下文自動推導函數式編程
(o1, o2) -> Integer.compare(o1.length(), o2.length());
也就變成了例子中的樣子。 其它簡化寫法:若是某個表達式不含有參數,你可使用一對空的小括號,跟無參方法同樣;若是隻含有一個參數,且能夠被推導,能夠不寫小括號。函數
() -> System.out.println("無參的 lambda 表達式"); event -> System.out.println("按鈕點擊");
這部分是理解 Java lambda 的重點 對於只包含一個抽象 方法的接口,你能夠經過 lambda 表達式建立該接口的對象。這種接口被稱爲函數式接口線程
上面已經提到,咱們有傳遞一段代碼的需求。一樣,Java 中不少已有的接口也須要封裝代碼塊,好比 Runnable、Comparator。使用 lambda 表達式實現本應由這些接口實例實現的功能的行爲,即lambda 表達式轉換成一個接口的實例叫函數式接口轉換(ps:我的理解,非標準定義)。 實際上完成函數式接口的轉換是 lambda 表達式在Java 中惟一能作的事。咱們以API 中的幾段代碼來講明什麼是函數式接口轉換:code
public void sort(Comparator<? super E> c) { final int expectedModCount = modCount; Arrays.sort((E[]) elementData, 0, size, c); if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } modCount++; }
上面一段代碼是 Arraylist 類中的 sort 方法,從方法簽名中的參數列表看,這個方法須要一個參數:比較器實現。回顧一下咱們最開始的例子,參數傳的是什麼? lambda 表達式對不對?
List<String> list = Arrays.asList("aaa", "bbb", "ccc"); list.sort((o1, o2) -> Integer.compare(o1.length(), o2.length()));
即咱們以 lambda 表達式實現了接口實例的功能。是怎麼實現的呢?list.sort 方法會接收一個實現了 Comparator 接口的類的實例,調用該對象的 compare 方法會執行 lambda 表達式中的代碼。
Java API 在 java.util.function 包中定義了一些通用的函數式接口,好比 BiFunction<T, U, R>意思是T,U類型的參數以及R類型的返回值。
BiFunction<String, String, Integer> c = (o1, o2) -> Integer.compare(o1.length(), o2.length());
雖然你可使用 BiFunction 聲明、引用一個表達式,可是並無什麼卵用,由於沒有任何一個方法能夠接收函數式接口做爲參數。
把一個已有的方法傳遞給其餘代碼。 好比說上面一直用的排序例子,咱們不按長度排序了,如今按字符串大小排序,而且忽略大小寫,代碼應該是這樣子的:
List<String> list = new ArrayList<>(); list.sort((String x, String y) -> x.compareToIgnoreCase(y));
其實咱們傳遞給sort 方法的代碼片斷就是 String 類的 compareToIgnoreCase 方法。爲了更優雅的寫代碼,API 簡化這種寫法,即
List<String> list = new ArrayList<>(); list.sort(String::compareToIgnoreCase);
::操做符分隔對象/類名 和 方法名,主要有三種狀況
list.forEach(System.out::print); // 至關於 list.forEach(x -> System.out.println(x));
對於第三種狀況,第一個參數會成爲執行方法的對象,第2個是方法的參數。好比上面的字符串排序 demo。
構造器引用與方法引用相似,不一樣的是構造器引用引用的方法名是 new。貌似用處不大,瞭解便可。
簡單地說,lambda 表達式的變量做用域和內部類的變量做用域相似。
static int b = 10; public static void main(String[] args) { int a = 100; Runnable r = () -> { a++; // 錯誤,不能更改局部變量的值 b++; // 正確,但有線程安全問題 int a = 1; // 錯誤,不能與局部變量重名 int b = 1; // 正確 }; new Thread(r).run(); }
接口能夠有實現方法,並且不須要實現類去實現其方法。只需在方法名前面加個default關鍵字便可。
爲何要定義默認方法? Java 的接口是不可擴展的,一旦你爲某個父接口增長了一個方法,你就必須爲全部的實現類增長這個方法的實現。 由於新增了對 lambda 表達式的支持,如今 Java 有了傳遞代碼塊的能力,因此對集合的處理有了更簡潔、高效的方式,好比,輸出集合元素:
// old for(String s : list) { System.out.println(s); } // now list.forEach(System.out::println);
可是,若是給Collection 或 Iterator 增長forEach 接口,會要求全部的實現類增長 forEach 的實現,顯然這是沒法接受的。因此,爲了解決接口的修改與現有的實現不兼容的問題,引入了默認方法。 在JVM中,默認方法的實現是很是高效的,而且經過字節碼指令爲方法調用提供了支持。 使用規則 若是接口接口定義了一個默認方法,而另外一個父類/接口有定義了一個同名方法,該選擇哪一個? 1.選擇父類中的方法。若是父類中提供了實現,忽略接口中的方法。 2.接口衝突。若是多個父接口中都提供了對同一方法的實現,接口中的默認方法都忽略,實現類必須本身定義實現。
Java 8 中,接口中容許添加靜態方法。好比咱們在 章節「5.3 比較器」 中使用到的 Comparator.comparing 方法。使用靜態方法,代碼會更簡潔。咱們再以開頭的那段代碼做爲例子:
List<String> list = Arrays.asList("aaa", "bbb", "ccc"); list.sort(new Comparator<String>() { @Override public int compare(String o1, String o2) { return Integer.compare(o1.length(), o2.length()); } });
能夠寫成
List<String> list = Arrays.asList("aaa", "bbb", "ccc"); list.sort(Comparator.comparing(String::length));