名詞王國裏的新政-解讀Java8之lambda表達式

前幾天在reddit上看到Java8 M8 Developer Preview版本已經發布了,難免想要嚐鮮一把。Developer Preview版本已經全部Feature都完成了,Java8的特性能夠在這裏看到http://openjdk.java.net/projects/jdk8/features,下載地址:http://jdk8.java.net/download.html。Java8最值得期待的就是lambda表達式了,本文就將帶你體驗lambda表達式,並進行比較深刻的解析。html

下載及配置

Intellij IDEA已經完美支持Java8了。首先打開Project Structure,在Project裏設置新的JDK路徑,並設置Modules=>Source=>Language Level爲8.0便可。java

如今咱們可使用Java8編寫程序了!可是當咱們開開心心編寫完,享受到高級的lambda表達式後,運行程序,會提示:java: Compilation failed: internal java compiler error!這是由於javacc的版本還不對,在Compiler=>Java Compiler裏將項目對應的javacc版本選爲1.8便可。git

什麼?你說你用Eclipse?好像目前尚未穩定版!想嚐鮮的,能夠看看這個地址http://stackoverflow.com/questions/13295275/programming-java-8-in-eclipse,大體是先checkout Eclipse JDT的beta java8分支,而後在Eclipse裏運行這個項目,從而啓動一個支持java8的Eclipse…不過應該難不倒做爲geek的你吧!github

體驗lambda表達式

好了,咱們開始體驗Java8的新特性-lambda表達式吧!如今咱們的匿名類能夠寫成這樣子了:編程

<!-- lang: java -->
    new Thread(() -> {
        System.out.println("Foo");
    }).start();

而以前的寫法只能是這樣子:閉包

<!-- lang: java -->
    new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("Foo");
        }
    }).start();

這樣一看,咱們彷佛就是匿名類寫起來簡單了一點啊?而第二種方法,藉助便捷的IDE,好像編寫效率也沒什麼差異?博主開始也是這樣認爲,仔細學習以後,才知道其中的奧妙所在!app

這裏有一個重要的信息,就是**()->{}這裏表明一個函數,而非一個對象。**可能這麼說比較抽象,咱們仍是代碼說話吧:dom

<!-- lang: java -->
public class LambdaTest {

    private static void bar(){
        System.out.println("bar");
    }

    public static void main(String[] args) {
        new Thread(LambdaTest::bar).start();
    }

}

看懂了麼?這裏LambdaTest::bar表明一個函數(用C++的同窗笑了),而new Thread(Runnable runnable)的參數,能夠接受是一個函數做爲參數!eclipse

是否是以爲很神奇,顛覆了Java思惟?在剖析原理之前,博主暫且賣個關子,咱們先來說講什麼是lambda表達式。編程語言

什麼是lambda表達式

lambda表達式的由來

絮叨幾句,現代編程語言的lamdba表達式都來自1930年代初,阿隆佐·邱奇(Alonzo Church)提出的λ演算(Lambda calculus)理論。λ演算的核心思想就是「萬物皆函數」。一個λ算子即一個函數,其通常形式是λx.x + 2。一個λ算子能夠做爲另外一個λ算子的輸入,從而構建一個高階的函數。λ演算是函數式編程的鼻祖,大名鼎鼎的編程語言Lisp就是基於λ演算而創建。用過Lisp的應該都清楚,它的語法很簡單,可是卻有包容萬物的能力。

可能搞計算機的對邱奇比較陌生,可是提起和邱奇同時代的另一我的,你們就會以爲如雷貫耳了,那就是阿蘭·圖靈。邱奇成名的時候,圖靈仍是個大學生。邱奇和圖靈一塊兒發表了邱奇-圖靈論題,並分別提出了λ演算和圖靈機,加上哥德爾提出的遞歸函數一塊兒,在理論上肯定了什麼是可計算性。至於什麼是可計算性,其實博主也說不清楚,可是現代全部計算機程序語言,均可以認爲是從三種之一發展而來,並與之等價的。僅此一點,其影響深遠,可想而知。當年教咱們《計算理論》的是一個德高望重的教授,人稱宋公,每次講到那個輝煌的年代,老是要停下來,神情專一的感嘆一句:「偉大啊!」想一想確實挺偉大,人家圖靈大學時候就奠基了現代計算機的基礎,而咱們那會大概還在打DOTA…

附上大神們的照片,你們感覺一下:

turing etc.

現代編程語言中的lambda表達式

好了扯遠了,神遊過了那個偉大的時代,咱們繼續思考如何編代碼作需求吧…

現代語言的lambda表達式,大概具有幾個特徵(博主本身概括的,若有不嚴謹,歡迎指正):

  1. 函數可做爲輸入;
  2. 函數可做爲輸出;
  3. 函數可做用在函數上,造成高階函數。
  4. 函數支持lambda格式的定義。

其實有了一、2,3也就是順水推舟的事情,而4其實沒有太大的必要性,由於通常語言都有本身的函數定義方式,4僅僅是做爲一種補充。固然實現了4的語言,通常都會說:「你看我實現了lambda表達式!」(望向Java8和Python同窗)

在Java8中使用lambda表達式

FunctionalInterface

Java中的lambda沒法單獨出現,它須要一個接口來盛放。這個接口必須使用@FunctionalInterface做爲註解,而且只有一個未實現的方法。等等,什麼叫接口中未實現的方法?難道接口中還能夠有已實現的方法?恭喜你,猜對了!Java8的接口也能夠寫實現了!是否是以爲Interface和AbstractClass更加傻傻分不清楚了?可是AbstractClass是沒法使用@FunctionalInterface註解的,官方的解釋是爲了防止AbstractClass的構造函數作一些事情,可能會致使一些調用者意料不到的事情發生。

好了,咱們來看一點代碼,Runnable接口如今變成了這個樣子:

<!-- lang: java -->
@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

這裏咱們能夠將任意無參數的lambda表達式賦值給Runnable:

<!-- lang: java -->
    Runnable runnable = () -> {
        System.out.println("Hello lambda!");
    };
    runnable.run();

lambda表達式本質上是一個函數,因此咱們還能夠用更加神奇的賦值:

<!-- lang: java -->
public class HelloLambda {

    private static void hellolambda() {
        System.out.println("Hello lambda!");
    }

    public static void main(String[] args) {
        Runnable runnable = HelloLambda::hellolambda;
        runnable.run();
    }
}

這裏看到這裏,你們大概明白了,lambda表達式其實只是個幌子,更深層次的含義是:函數在Java裏面能夠做爲一個實體進行表示了。這就意味着,在Java8裏,函數既能夠做爲函數的參數,也能夠做爲函數的返回值,即具備了lambda演算的全部特性。

Function系列API

看到這裏,可能你們會有疑問?什麼樣的函數和什麼樣的lambda表達式屬於同一類型?答案是參數和返回值的類型共同決定函數的類型。例如Runnable的run方法不接受參數,也沒有返回值,那麼Runnable接口則能夠用任意沒有參數且沒有返回值的函數來賦值。這樣概念上來講,Runnable表示的含義就從一個對象變成了一個方法

這一點在Java8中的java.util.function包裏的代碼獲得了驗證。以最具備表明性的Function接口爲例:

<!-- lang: java -->
@FunctionalInterface
public interface Function<T, R> {

    R apply(T t);

}

有了Function,咱們能夠這樣寫:

<!-- lang: java -->
    Function<Integer,String> convert = String::valueOf;
    String s = convert.apply(1);

這個東東是否是很像Javascript中的函數對象?

惋惜的是,這裏的Function算是個半成品,它只能表示一個有單個參數,並有非void返回值的函數。像System.out.println()這種方法,由於返回值爲void,是沒法賦值爲Function的!

怎麼辦?java.util.function包提供了一個不那麼完美的解決方案:多定義幾個FunctionalInterface唄!

因而,在Java8裏有了:

  • Supplier: 沒有參數,只有返回值的函數
  • Consumer: 一個參數,返回值爲void的函數
  • BiFunction: 兩個參數,一個返回值的函數
  • BiConsumer: 兩個參數,沒有返回值的函數
  • ...

對於這些個API,我也沒有什麼力氣吐槽了,反正我也想不出更好的方法…你們趁機,多學幾個單詞吧,嗯。

總結:名詞王國的新政

相信不少同窗都看過這篇著名的文章:名詞王國裏的死刑。這篇文章吐槽了Java裏,動詞(方法)在Java里老是要依附於某個名詞(對象/類)存在。

如今動詞在名詞王國終於有了一個身份了。固然這個動詞須要先取得一個名詞的身份(FunctionInterface),而後才能名正言順的倖存下來。好在Oracle國王預先爲他們留了一些身份(Function、Consumer、Supplier、BiFunction...),因此大多數動詞都已經找到了本身的位置。System.out.println(String)如今是Consumer<String>了,String.valueOf(Integer)如今是Function<Integer,String>了,Collection.size()如今是Supplier<Integer>了…。要爲一些較長參數的方法獲取一個身份,也是挺容易的(定義一個新的FunctionInterface接口)。

我相信這個影響是深遠的。例以下面一段代碼,能夠同一行代碼將一個List<Integer>轉換成一個List<String>:

<!-- lang: java -->
List<String> strings = intList.stream().map(String::valueOf).collect(Collectors.<String>toList());

固然問題也存在。由於包含了閉包等因素,FunctionInterface的序列化/反序列化會是一個至關複雜的事情。熟悉Java的開發者,也會由於lambda的引入,帶來了一些困惑。俗話說活到老學到老,我卻是不介意這個新功能,你說呢?

參考文獻:

  1. http://blog.sciencenet.cn/blog-414166-628109.html
  2. http://www.global-sci.org/mc/issues/3/no2/freepdf/80s.pdf
  3. http://en.wikipedia.org/wiki/Lambda_calculus

本系列文章還有餘下幾部分,敬請期待:

lambda表達式與閉包

Java8 lambda表達式原理分析

相關文章
相關標籤/搜索