Java是一門面向對象編程語言。面向對象編程語言和函數式編程語言中的基本元素(Basic Values)均可以動態封裝程序行爲:面向對象編程語言使用帶有方法的對象封裝行爲,函數式編程語言使用函數封裝行爲。
但這個相同點並不明顯,由於Java的對象每每比較「重量級」:實例化一個類型每每會涉及不一樣的類,並須要初始化類裏的字段和方法。因而對於有些Java對象,只是對單個函數的封裝。 下面這個典型用例:Java API中定義了一個接口(通常被稱爲回調接口),用戶經過提供這個接口的實例來傳入指定行爲,例如:java
public interface ActionListener { void actionPerformed(ActionEvent e); }
這裏並不須要專門定義一個類來實現ActionListener接口,由於它只會在調用處被使用一次。用戶通常會使用匿名類型把行爲內聯(inline):express
button.addActionListener(new ActionListener) { public void actionPerformed(ActionEvent e) { ui.dazzle(e.getModifiers()); } }
顯然匿名內部類並非一個好的選擇,其語法過於冗餘,匿名類中的this和變量名容易令人產生誤解,類型載入和實例建立語義不夠靈活,沒法捕獲非final的局部變量,沒法對控制流進行抽象等等等等。編程
因而java8引入了一個函數式接口的概念。理解Functional Interface(函數式接口,如下簡稱FI)是學習Java8 Lambda表達式的關鍵所在,因此放在最開始討論。FI的定義其實很簡單:任何接口,若是隻包含惟一一個抽象方法,那麼它就是一個FI。數據結構
Java8提供了@FunctionalInterface註解。舉個簡單的例子,Runnable接口就是一個FI,下面是它的源代碼:app
@FunctionalInterface public interface Runnable { public abstract void run(); }
咱們並不須要額外的工做來聲明一個接口是函數式接口:編譯器會根據接口的結構自行判斷(判斷過程並不是簡單的對接口方法計數:一個接口可能冗餘的定義了一個Object已經提供的方法,好比toString(),或者定義了靜態方法或默認方法,這些都不屬於函數式接口方法的範疇)。不過API做者們能夠經過@FunctionalInterface註解來顯式指定一個接口是函數式接口(以免無心聲明瞭一個符合函數式標準的接口),加上這個註解以後,編譯器就會驗證該接口是否知足函數式接口的要求。編程語言
下面是Java SE 7中已經存在的函數式接口:ide
java.lang.Runnable java.util.concurrent.Callable java.security.PrivilegedAction java.util.Comparator java.io.FileFilter java.beans.PropertyChangeListener
Predicate<T>——接收T對象並返回boolean Consumer<T>——接收T對象,不返回值 Function<T, R>——接收T對象,返回R對象 Supplier<T>——提供T對象(例如工廠),不接收值 UnaryOperator<T>——接收T對象,返回T對象 BinaryOperator<T>——接收兩個T對象,返回T對象
下面簡單介紹幾個:函數
@FunctionalInterface //Predicate用來判斷一個對象是否知足某種條件,好比,單詞是否由六個以上字母組成: public interface Predicate<T> { boolean test(T t); } words.stream().filter(word -> word.length() > 6).count(); @FunctionalInterface //Function表示接收一個參數,併產生一個結果的函數: public interface Function<T, R> { R apply(T t); } //下面的例子將集合裏的每個整數都乘以2: ints.stream().map(x -> x * 2); @FunctionalInterface //Consumer表示對單個參數進行的操做,前面例子中的forEach()方法接收的參數就是這種操做: public interface Consumer<T> { void accept(T t); }
爲了可以方便、快捷、幽雅的建立出FI的實例,Java8提供了Lambda表達式這顆語法糖。下面我用一個例子來介紹Lambda語法。假設咱們想對一個List<String>按字符串長度進行排序,那麼在Java8以前,能夠藉助匿名內部類來實現:性能
List<String> words = Arrays.asList("apple", "banana", "pear"); words.sort(new Comparator<String>() { @Override public int compare(String w1, String w2) { return Integer.compare(w1.length(), w2.length()); } });
上面的匿名內部類簡直能夠用醜陋來形容,惟一的一行邏輯被五行垃圾代碼淹沒。根據前面的定義(並查看Java源代碼)可知,Comparator是個FI,因此,能夠用Lambda表達式來實現:
List<String> words = Arrays.asList("apple", "banana", "pear"); words.sort((String w1, String w2) -> { return Integer.compare(w1.length(), w2.length()); });
lambda表達式的語法由參數列表、箭頭符號->和函數體組成。函數體既能夠是一個表達式,也能夠是一個語句塊:
表達式:表達式會被執行而後返回執行結果。
語句塊:語句塊中的語句會被依次執行,就像方法中的語句同樣——
return語句會把控制權交給匿名方法的調用者
break和continue只能在循環中使用
若是函數體有返回值,那麼函數體內部的每一條路徑都必須返回值
表達式函數體適合小型lambda表達式,它消除了return關鍵字,使得語法更加簡潔。
下面是一些出如今語句中的lambda表達式:
FileFilter java = (File f) -> f.getName().endsWith("*.java"); String user = doPrivileged(() -> System.getProperty("user.name")); new Thread(() -> { connectToService(); sendNotification(); }).start();
對於給定的lambda表達式,其類型都由上下文推導而出,例如:
ActionListener l = (ActionEvent e) -> ui.dazzle(e.getModifiers()); //ActionListener的實例 Callable<String> c = () -> "done"; //Callable的實例 PrivilegedAction<String> a = () -> "done"; //PrivilegedAction的實例
lambda表達式對目標類型也是有要求的。編譯器會檢查lambda表達式的類型和目標類型的方法簽名(method signature)是否一致。當且僅當下面全部條件均知足時,lambda表達式才能夠被賦給目標類型T:
而且lambda表達式的參數類型能夠從目標類型中得出
Comparator<String> c = (s1, s2) -> s1.compareToIgnoreCase(s2);
在上面的例子裏,編譯器能夠推導出s1和s2的類型是String。此外,當lambda的參數只有一個並且它的類型能夠被推導得知時,該參數列表外面的括號能夠被省略:
FileFilter java = f -> f.getName().endsWith(".java");
有時候Lambda表達式的代碼就只是一個簡單的方法調用而已,遇到這種狀況,Lambda表達式還能夠進一步簡化爲 方法引用(Method References) 。一共有四種形式的方法引用,第一種引用 靜態方法 ,例如:
List<Integer> ints = Arrays.asList(1, 2, 3); ints.sort(Integer::compare);
第二種引用 某個特定對象的實例方法 ,例如前面那個遍歷並打印每個word的例子能夠寫成這樣:
words.forEach(System.out::println);
第三種引用 某個類的實例方法,例如:
words.stream().map(word -> word.length()); // lambda words.stream().map(String::length); // method reference
第四種引用類的 構造函數 ,例如:
// lambda words.stream().map(word -> { return new StringBuilder(word); }); // constructor reference words.stream().map(StringBuilder::new);
咱們在設計lambda時的一個重要目標就是新增的語言特性和庫特性可以無縫結合(designed to work together)。接下來,咱們經過一個實際例子(按照姓對名字列表進行排序)來演示這一點: 好比說下面的代碼:
List<Person> people = ... Collections.sort(people, new Comparator<Person>() { public int compare(Person x, Person y) { return x.getLastName().compareTo(y.getLastName()); } })
冗餘代碼實在太多了!有了lambda表達式,咱們能夠去掉冗餘的匿名類:
Collections.sort(people, (Person x, Person y) -> x.getLastName().compareTo(y.getLastName()));
儘管代碼簡潔了不少,但它的抽象程度依然不好:開發者仍然須要進行實際的比較操做(並且若是比較的值是原始類型那麼狀況會更糟),因此咱們要藉助Comparator裏的comparing方法實現比較操做:
Collections.sort(people, Comparator.comparing((Person p) -> p.getLastName()));
在類型推導和靜態導入的幫助下,咱們能夠進一步簡化上面的代碼:
Collections.sort(people, comparing(p -> p.getLastName()));
咱們注意到這裏的lambda表達式其實是getLastName的代理(forwarder),因而咱們能夠用方法引用代替它:
Collections.sort(people, comparing(Person::getLastName));
最後,使用Collections.sort這樣的輔助方法並非一個好主意:它不但使代碼變的冗餘,也沒法爲實現List接口的數據結構提供特定(specialized)的高效實現,並且因爲Collections.sort方法不屬於List接口,用戶在閱讀List接口的文檔時不會察覺在另外的Collections類中還有一個針對List接口的排序(sort())方法。 默認方法能夠有效的解決這個問題,咱們爲List增長默認方法sort(),而後就能夠這樣調用:
people.sort(comparing(Person::getLastName));;
此外,若是咱們爲Comparator接口增長一個默認方法reversed()(產生一個逆序比較器),咱們就能夠很是容易的在前面代碼的基礎上實現降序排序。
people.sort(comparing(Person::getLastName).reversed());;