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

Lambda表達(dá)式

要理解lambda表達(dá)式,首先要了解的是函數(shù)式接口(functional interface)能扒。簡(jiǎn)單來說迟郎,函數(shù)式接口是只包含一個(gè)抽象方法的接口。比如Java標(biāo)準(zhǔn)庫(kù)中的java.lang.Runnablejava.util.Comparator都是典型的函數(shù)式接口透硝。對(duì)于函數(shù)式接口狰闪,除了可以使用Java中標(biāo)準(zhǔn)的方法來創(chuàng)建實(shí)現(xiàn)對(duì)象之外,還可以使用lambda表達(dá)式來創(chuàng)建實(shí)現(xiàn)對(duì)象濒生。這可以在很大程度上簡(jiǎ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á)式來完成同樣的功能磺浙,得到的代碼非常簡(jiǎn)潔,如下面所示:

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

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

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

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

在Java SE 8之前的標(biāo)準(zhǔn)庫(kù)中包含的函數(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)的對(duì)列表進(jìn)行map操作的方法龙优。從代碼中可以看到,如果盡可能的使用函數(shù)式接口事秀,則代碼使用起來會(huì)非常簡(jiǎn)潔彤断。

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)建對(duì)象的情況下引用一個(gè)構(gòu)造方法易迹。方法引用是另外一種實(shí)現(xiàn)函數(shù)式接口的方法宰衙。在某些情況下,方法引用可以進(jìn)一步簡(jiǎn)化代碼睹欲。比如下面的代碼中供炼,第一個(gè)forEach方法調(diào)用使用的是lambda表達(dá)式,第二個(gè)使用的是方法引用窘疮。兩者作用相同袋哼,不過使用方法引用的做法更加簡(jiǎn)潔。

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)的修改。如果在新版本中對(duì)接口進(jìn)行了修改衅胀,會(huì)導(dǎo)致早期版本的代碼無法運(yùn)行岔乔。Java對(duì)于接口更新的限制過于嚴(yán)格。在代碼演化的過程中滚躯,一般所遵循的原則是不刪除或修改已有的功能雏门,而是添加新的功能作為替代。已有代碼可以繼續(xù)使用原有的功能掸掏,而新的代碼則可以使用新的功能茁影。但是這種更新方式對(duì)于接口是不適用的,因?yàn)橥粋€(gè)接口中添加新的方法也會(huì)導(dǎo)致已有代碼無法運(yùn)行丧凤。
接口的默認(rèn)方法的主要目標(biāo)之一是解決接口的演化問題募闲。當(dāng)往一個(gè)接口中添加新的方法時(shí),可以提供該方法的默認(rèn)實(shí)現(xiàn)愿待。對(duì)于已有的接口使用者來說浩螺,代碼可以繼續(xù)運(yùn)行。新的代碼則可以使用該方法仍侥,也可以覆寫默認(rèn)的實(shí)現(xiàn)要出。
考慮下面的一個(gè)簡(jiǎn)單的進(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ù)提供了新的批量處理的功能况脆,允許在一次請(qǐng)求中同時(shí)轉(zhuǎn)換多個(gè)數(shù)值。最直接的做法是在原有的接口中添加一個(gè)新的方法來支持批量處理批糟,不過這樣會(huì)造成已有的代碼無法運(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語(yǔ)言只允許類之間的單繼承關(guān)系,但是一個(gè)類可以實(shí)現(xiàn)多個(gè)接口棠隐。在默認(rèn)方法引入之后石抡,接口中不僅可以包含變量和方法聲明,還可以包含方法體助泽,也就是行為啰扛。通過實(shí)現(xiàn)多個(gè)接口嚎京,一個(gè)Java類實(shí)際上可以獲得來自不同接口的行為。這種功能類似于JavaScript等其他語(yǔ)言中可見的“混入類”(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)庫(kù)已經(jīng)使用默認(rèn)方法來對(duì)集合類中的接口進(jìn)行更新。比如java.util.Collection接口中新增的默認(rèn)方法removeIf可以刪除集合中滿足某些條件的元素土全。還有java.lang.Iterable接口中新增的默認(rèn)方法forEach可以遍歷集合中的元素捎琐,并執(zhí)行一些操作。這些新增的默認(rèn)方法大多使用了java.util.function包中的函數(shù)式接口裹匙,因此可以使用lambda表達(dá)式來非常簡(jiǎn)潔的進(jìn)行操作瑞凑。
Lambda表達(dá)式是Java SE 8在提高開發(fā)人員生產(chǎn)效率上的一個(gè)重大改進(jìn)。通過語(yǔ)法上的改進(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的方法引用更方便,方法引用由::雙冒號(hào)操作符標(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)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末技掏,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子项鬼,更是在濱河造成了極大的恐慌哑梳,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绘盟,死亡現(xiàn)場(chǎng)離奇詭異鸠真,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)龄毡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門吠卷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人沦零,你說我怎么就攤上這事祭隔。” “怎么了路操?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵疾渴,是天一觀的道長(zhǎng)千贯。 經(jīng)常有香客問我,道長(zhǎng)程奠,這世上最難降的妖魔是什么丈牢? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮瞄沙,結(jié)果婚禮上己沛,老公的妹妹穿的比我還像新娘。我一直安慰自己距境,他們只是感情好申尼,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著垫桂,像睡著了一般师幕。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上诬滩,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天霹粥,我揣著相機(jī)與錄音,去河邊找鬼疼鸟。 笑死后控,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的空镜。 我是一名探鬼主播浩淘,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼吴攒!你這毒婦竟也來了张抄?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤洼怔,失蹤者是張志新(化名)和其女友劉穎署惯,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體镣隶,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡泽台,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了矾缓。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡稻爬,死狀恐怖嗜闻,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情桅锄,我是刑警寧澤琉雳,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布样眠,位于F島的核電站,受9級(jí)特大地震影響翠肘,放射性物質(zhì)發(fā)生泄漏檐束。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一束倍、第九天 我趴在偏房一處隱蔽的房頂上張望被丧。 院中可真熱鬧,春花似錦绪妹、人聲如沸甥桂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)黄选。三九已至,卻和暖如春婶肩,著一層夾襖步出監(jiān)牢的瞬間办陷,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來泰國(guó)打工律歼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留民镜,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓苗膝,卻偏偏與公主長(zhǎng)得像殃恒,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子辱揭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

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