Java SE 8: Lambda表達(dá)式

Lambda表達(dá)式

要理解lambda表達(dá)式,首先要了解的是函數(shù)式接口(functional interface)。簡單來說,函數(shù)式接口是只包含一個(gè)抽象方法的接口。比如Java標(biāo)準(zhǔn)庫中的java.lang.Runnablejava.util.Comparator都是典型的函數(shù)式接口。對于函數(shù)式接口,除了可以使用Java中標(biāo)準(zhǔn)的方法來創(chuàng)建實(shí)現(xiàn)對象之外,還可以使用lambda表達(dá)式來創(chuàng)建實(shí)現(xiàn)對象。這可以在很大程度上簡化代碼的實(shí)現(xiàn)。在使用lambda表達(dá)式時(shí),只需要提供形式參數(shù)和方法體。由于函數(shù)式接口只有一個(gè)抽象方法,所以通過lambda表達(dá)式聲明的方法體就肯定是這個(gè)唯一的抽象方法的實(shí)現(xiàn),而且形式參數(shù)的類型可以根據(jù)方法的類型聲明進(jìn)行自動(dòng)推斷。

以Runnable接口為例來進(jìn)行說明,傳統(tǒng)的創(chuàng)建一個(gè)線程并運(yùn)行的方式如下所示:

public void runThread() {
    new Thread(new Runnable() {
        public void run() {
            System.out.println("Run!");
        }
    }).start();
}

在上面的代碼中,首先需要?jiǎng)?chuàng)建一個(gè)匿名內(nèi)部類實(shí)現(xiàn)Runnable接口,還需要實(shí)現(xiàn)接口中的run方法。如果使用lambda表達(dá)式來完成同樣的功能,得到的代碼非常簡潔,如下面所示:

public void runThreadUseLambda() {
    new Thread(() -> {
        System.out.println("Run!");
    }).start();
}

相對于傳統(tǒng)的方式,lambda表達(dá)式在兩個(gè)方面進(jìn)行了簡化:首先是Runnable接口的聲明,這可以通過對上下文環(huán)境進(jìn)行推斷來得出;其次是對run方法的實(shí)現(xiàn),因?yàn)楹瘮?shù)式接口中只包含一個(gè)需要實(shí)現(xiàn)的方法。

Lambda表達(dá)式的聲明方式比較簡單,由形式參數(shù)和方法體兩部分組成,中間通過->分隔。形式參數(shù)不需要包含類型聲明,可以進(jìn)行自動(dòng)推斷。當(dāng)然在某些情況下,形式參數(shù)的類型聲明是不可少的。方法體則可以是簡單的表達(dá)式或代碼塊。

Collections.sort(list, (x, y) -> y - x);

在Java SE 8之前的標(biāo)準(zhǔn)庫中包含的函數(shù)式接口并不多。Java SE 8增加了java.util.function包,里面都是可以在開發(fā)中使用的函數(shù)式接口。開發(fā)人員也可以創(chuàng)建新的函數(shù)式接口。最好在接口上使用注解@FunctionalInterface進(jìn)行聲明,以免團(tuán)隊(duì)的其他人員錯(cuò)誤地往接口中添加新的方法。
下面的代碼使用函數(shù)式接口java.util.function.Function實(shí)現(xiàn)的對列表進(jìn)行map操作的方法。從代碼中可以看到,如果盡可能的使用函數(shù)式接口,則代碼使用起來會非常簡潔。

public class CollectionUtils {
    public static  List map(List input, Function processor) {
        ArrayList result = new ArrayList();
        for (T obj : input) {
            result.add(processor.apply(obj));
        }
        return result;
    }
    
    public static void main(String[] args) {
        List input = Arrays.asList(new String[] {"apple", "orange", "pear"});
        List lengths = CollectionUtils.map(input, (String v) -> v.length());
        List uppercases = CollectionUtils.map(input, (String v) -> v.toUpperCase());
    }
}

方法和構(gòu)造方法引用

方法引用可以在不調(diào)用某個(gè)方法的情況下引用一個(gè)方法。構(gòu)造方法引用可以在不創(chuàng)建對象的情況下引用一個(gè)構(gòu)造方法。方法引用是另外一種實(shí)現(xiàn)函數(shù)式接口的方法。在某些情況下,方法引用可以進(jìn)一步簡化代碼。比如下面的代碼中,第一個(gè)forEach方法調(diào)用使用的是lambda表達(dá)式,第二個(gè)使用的是方法引用。兩者作用相同,不過使用方法引用的做法更加簡潔。

List input = Arrays.asList(new String[] {"apple", "orange", "pear"});
input.forEach((v) -> System.out.println(v));
input.forEach(System.out::println);

構(gòu)造方法可以通過名稱“new”來進(jìn)行引用,如下面的代碼所示:

List dateValues = Arrays.asList(new Long[] {0L, 1000L});
List dates = CollectionUtils.map(dateValues, Date::new);

接口的默認(rèn)方法

Java開發(fā)中所推薦的實(shí)踐是面向接口而不是實(shí)現(xiàn)來編程。接口作為不同組件之間的契約,使得接口的實(shí)現(xiàn)可以不斷地演化。不過接口本身的演化則比較困難。當(dāng)接口發(fā)生變化時(shí),該接口的所有實(shí)現(xiàn)類都需要做出相應(yīng)的修改。如果在新版本中對接口進(jìn)行了修改,會導(dǎo)致早期版本的代碼無法運(yùn)行。Java對于接口更新的限制過于嚴(yán)格。在代碼演化的過程中,一般所遵循的原則是不刪除或修改已有的功能,而是添加新的功能作為替代。已有代碼可以繼續(xù)使用原有的功能,而新的代碼則可以使用新的功能。但是這種更新方式對于接口是不適用的,因?yàn)橥粋€(gè)接口中添加新的方法也會導(dǎo)致已有代碼無法運(yùn)行。
接口的默認(rèn)方法的主要目標(biāo)之一是解決接口的演化問題。當(dāng)往一個(gè)接口中添加新的方法時(shí),可以提供該方法的默認(rèn)實(shí)現(xiàn)。對于已有的接口使用者來說,代碼可以繼續(xù)運(yùn)行。新的代碼則可以使用該方法,也可以覆寫默認(rèn)的實(shí)現(xiàn)。
考慮下面的一個(gè)簡單的進(jìn)行貨幣轉(zhuǎn)換的接口。該接口的實(shí)現(xiàn)方式可能是調(diào)用第三方提供的服務(wù)來完成實(shí)際的轉(zhuǎn)換操作。

public interface CurrencyConverter {
    BigDecimal convert(Currency from, Currency to, BigDecimal amount);
}

該接口在開發(fā)出來之后,在應(yīng)用中得到了使用。在后續(xù)的版本更新中,第三方服務(wù)提供了新的批量處理的功能,允許在一次請求中同時(shí)轉(zhuǎn)換多個(gè)數(shù)值。最直接的做法是在原有的接口中添加一個(gè)新的方法來支持批量處理,不過這樣會造成已有的代碼無法運(yùn)行。而默認(rèn)方法則可以很好的解決這個(gè)問題。使用默認(rèn)方法的新接口如下所示。

public interface CurrencyConverter {
    BigDecimal convert(Currency from, Currency to, BigDecimal amount);

    default List convert(Currency from, Currency to, List amounts) {
        List result = new ArrayList();
            for (BigDecimal amount : amounts) {
                result.add(convert(from, to, amount));
            }
            return result;
    }
}

新添加的方法使用default關(guān)鍵詞來修飾,并可以有自己的方法體。
默認(rèn)方法的另外一個(gè)作用是實(shí)現(xiàn)行為的多繼承。Java語言只允許類之間的單繼承關(guān)系,但是一個(gè)類可以實(shí)現(xiàn)多個(gè)接口。在默認(rèn)方法引入之后,接口中不僅可以包含變量和方法聲明,還可以包含方法體,也就是行為。通過實(shí)現(xiàn)多個(gè)接口,一個(gè)Java類實(shí)際上可以獲得來自不同接口的行為。這種功能類似于JavaScript等其他語言中可見的“混入類”(mixin)。實(shí)際上,Java中一直存在“常量接口(Constant Interface)”的用法。常量接口中只包含常量的聲明。通過實(shí)現(xiàn)這樣的接口,就可以直接引用這些常量。通過默認(rèn)方法,可以創(chuàng)建出類似的幫助接口,即接口中包含的都是通過默認(rèn)方法實(shí)現(xiàn)的幫助方法。比如創(chuàng)建一個(gè)StringUtils接口包含各種與字符串操作相關(guān)的默認(rèn)方法。通過繼承該接口就可以直接使用這些方法。
Java SE 8標(biāo)準(zhǔn)庫已經(jīng)使用默認(rèn)方法來對集合類中的接口進(jìn)行更新。比如java.util.Collection接口中新增的默認(rèn)方法removeIf可以刪除集合中滿足某些條件的元素。還有java.lang.Iterable接口中新增的默認(rèn)方法forEach可以遍歷集合中的元素,并執(zhí)行一些操作。這些新增的默認(rèn)方法大多使用了java.util.function包中的函數(shù)式接口,因此可以使用lambda表達(dá)式來非常簡潔的進(jìn)行操作。
Lambda表達(dá)式是Java SE 8在提高開發(fā)人員生產(chǎn)效率上的一個(gè)重大改進(jìn)。通過語法上的改進(jìn),可以減少開發(fā)人員需要編寫和維護(hù)的代碼數(shù)量。

列舉

// Java 8之前:
new Thread(new Runnable() {
    @Override
    public void run() {
    System.out.println("test");
    }
}).start();

//Java 8方式:
new Thread(() -> System.out.println("test0") ).start();
// Java 8之前:
JButton show =  new JButton("Show");
show.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
    System.out.println("test1");
    }
});


// Java 8方式:
show.addActionListener((e) -> {
    System.out.println("test1");
});
// Java 8之前:
List features = Arrays.asList("0", "1", "2", "3");
for (String feature : features) {
    System.out.println(feature);
}


// Java 8之后:
List features = Arrays.asList("0", "1", "2", "3");
features.forEach(n -> System.out.println(n));
 
// 使用Java 8的方法引用更方便,方法引用由::雙冒號操作符標(biāo)示,
// 看起來像C++的作用域解析運(yùn)算符
features.forEach(System.out::println);

// Java 8之前:
public static void filter(List names, Predicate condition) {
    for(String name: names)  {
        if(condition.test(name)) {
            System.out.println(name + " ");
        }
    }
}


// Java 8之后:
public static void filter(List names, Predicate condition) {
    names.stream().filter((name) -> (condition.test(name))).forEach((name) -> {
        System.out.println(name + " ");
    });
}
List costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);
// Java 8之前:
for (Integer cost : costBeforeTax) {
    double price = cost * 2;
    System.out.println(price);
}
 
// Java 8之后:
List costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);
costBeforeTax.stream().map((cost) -> cost * 2).forEach(System.out::println);
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,431評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,637評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,555評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,900評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,629評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,976評論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,976評論 3 448
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,139評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,686評論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,411評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,641評論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,129評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,820評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,233評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,567評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,362評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,604評論 2 380

推薦閱讀更多精彩內(nèi)容