Lambda表達(dá)式
要理解lambda表達(dá)式,首先要了解的是函數(shù)式接口(functional interface)能扒。簡(jiǎn)單來說迟郎,函數(shù)式接口是只包含一個(gè)抽象方法的接口。比如Java標(biāo)準(zhǔn)庫(kù)中的java.lang.Runnable和java.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);