題記
在閱讀JDK源碼java.util.Collections的時(shí)候在UnmodifiableCollection類中看到了這么一段代碼:
public void forEach(Consumer<? super E> action) { c.forEach(action); }
而Consumer的源碼如下:
@FunctionalInterface public interface Consumer<T> { void accept(T t); default Consumer<T> andThen(Consumer<? super T> after) { Objects.requireNonNull(after); return (T t) -> { accept(t); after.accept(t); }; } }
乍一看讓我費(fèi)解了一下,但是回過(guò)神來(lái)發(fā)現(xiàn)這不就是Java8的新特性Lambda表達(dá)式嗎。原來(lái)對(duì)于這些新特性只是了解一下,沒(méi)注意到在JDK源碼中也使用到了,所以抽時(shí)間看了一下Java的Lambda表達(dá)式。
Lambda演算
Lambda演算在wiki上的非形式化表述如下:
在λ演算中,每個(gè)表達(dá)式都代表一個(gè)函數(shù),這個(gè)函數(shù)有一個(gè)參數(shù),并且返回一個(gè)值。不論是參數(shù)和返回值,也都是一個(gè)單參的函數(shù)。可以這么說(shuō),λ演算中,只有一種“類型”,那就是這種單參函數(shù)。
函數(shù)是通過(guò)λ表達(dá)式匿名地定義的,這個(gè)表達(dá)式說(shuō)明了此函數(shù)將對(duì)其參數(shù)進(jìn)行什么操作。例如,“加2”函數(shù)f(x)= x + 2可以用lambda演算表示為λx.x + 2 (或者λy.y + 2,參數(shù)的取名無(wú)關(guān)緊要)而f(3)的值可以寫作(λx.x + 2) 3。函數(shù)的應(yīng)用(application)是左結(jié)合的:f x y =(f x) y。
考慮這么一個(gè)函數(shù):它把一個(gè)函數(shù)作為參數(shù),這個(gè)函數(shù)將被作用在3上:λf.f 3。如果把這個(gè)(用函數(shù)作參數(shù)的)函數(shù)作用于我們先前的“加2”函數(shù)上:(λf.f 3)(λx.x+2),則明顯地,下述三個(gè)表達(dá)式:
(λf.f 3)(λx.x+2) 與 (λx.x + 2) 3 與 3 + 2
是等價(jià)的。有兩個(gè)參數(shù)的函數(shù)可以通過(guò)lambda演算這么表達(dá):一個(gè)單一參數(shù)的函數(shù)的返回值又是一個(gè)單一參數(shù)的函數(shù)(參見(jiàn)Currying)。例如,函數(shù)f(x, y) = x - y可以寫作λx.λy.x - y。下述三個(gè)表達(dá)式:
(λx.λy.x - y) 7 2 與 (λy.7 - y) 2 與 7 - 2
也是等價(jià)的。然而這種lambda表達(dá)式之間的等價(jià)性無(wú)法找到一個(gè)通用的函數(shù)來(lái)判定。
詳細(xì)的形式化表述請(qǐng)?zhí)D(zhuǎn)Lambda演算
Java中的Lambda表達(dá)式
在Java中Lambda表達(dá)式可以有多個(gè)參數(shù),在JSR335-FINAL(Java Specification Requests)中對(duì)Java中Lambda表達(dá)式的形式定義如下:
LambdaExPRession: LambdaParameters '->' LambdaBody LambdaParameters: Identifier '(' FormalParameterListopt ')' '(' InferredFormalParameterList ')' InferredFormalParameterList: Identifier InferredFormalParameterList ',' Identifier LambdaBody: Expression Block The following definitions from 8.4.1 are repeated here for convenience: FormalParameterList: LastFormalParameter FormalParameters ',' LastFormalParameter FormalParameters: FormalParameter FormalParameters, FormalParameter FormalParameter: VariableModifiersopt Type VariableDeclaratorId LastFormalParameter: VariableModifiersopt Type '...' VariableDeclaratorId FormalParameter
舉例如下 Examples of lambda expressions:
() -> {} // No parameters; result is void () -> 42 // No parameters, expression body () -> null // No parameters, expression body () -> { return 42; } // No parameters, block body with return () -> { System.gc(); } // No parameters, void block body () -> { if (true) return 12; else { int result = 15; for (int i = 1; i < 10; i++) result *= i; return result; } } // Complex block body with returns (int x) -> x+1 // Single declared-type parameter (int x) -> { return x+1; } // Single declared-type parameter (x) -> x+1 // Single inferred-type parameter x -> x+1 // Parens optional for single inferred-type case (String s) -> s.length() // Single declared-type parameter (Thread t) -> { t.start(); } // Single declared-type parameter s -> s.length() // Single inferred-type parameter t -> { t.start(); } // Single inferred-type parameter (int x, int y) -> x+y // Multiple declared-type parameters (x,y) -> x+y // Multiple inferred-type parameters (final int x) -> x+1 // Modified declared-type parameter (x, final y) -> x+y // Illegal: can't modify inferred-type parameters (x, int y) -> x+y // Illegal: can't mix inferred and declared types注意,在形式參數(shù)中推導(dǎo)參數(shù)和聲明參數(shù)不能混用。(Inferred-type parameters的類型是編譯的時(shí)候從上下問(wèn)中推斷出來(lái)的,比如說(shuō)是借口定義時(shí)指定的參數(shù))
Java SE 8: Lambda Quick Start
以下例子摘自 Oracle-Java SE 8: Lambda Quick Start
Runnable Lambda
public class LambdaTest { public static void main(String[] args) { LambdaTest LT = new LambdaTest(); LT.runnableTest(); LT.comparatorTest(); } public void runnableTest() { System.out.println("=== RunnableTest ==="); // Anonymous Runnable Runnable r1 = new Runnable() { @Override public void run() { System.out.println("Hello world one!"); } }; // Lambda Runnable Runnable r2 = () -> { System.out.println("Hello world two!"); System.out.println("Hello world three!"); }; // Run em! r1.run(); r2.run(); } }
上面代碼用Lambda表達(dá)式代替了*New*操作和*run*方法的定義,使得代碼更為簡(jiǎn)潔。
Comparator Lambda
class Person { public String surName; public Person(String surName) { super(); this.surName = surName; } public void printName() { System.out.println(this.surName); } } public void comparatorTest() { List<Person> personList = new ArrayList<Person>(); personList.add(new Person("B")); personList.add(new Person("A")); // // Sort with Inner Class // Collections.sort(personList, new Comparator<Person>() { // public int compare(Person p1, Person p2) { // return p1.surName.compareTo(p2.surName); // } // }); // // System.out.println("=== Sorted Asc SurName ==="); // for (Person p : personList) { // p.printName(); // } // Use Lambda instead // Print Asc System.out.println("=== Sorted Asc SurName ==="); Collections.sort(personList, (p1, p2) -> p1.surName.compareTo(p2.surName)); for (Person p : personList) { p.printName(); } // Print Desc System.out.println("=== Sorted Desc SurName ==="); Collections.sort(personList, (p1, p2) -> p2.surName.compareTo(p1.surName)); for (Person p : personList) { p.printName(); } }
這里則是用Lambda表達(dá)式代替了匿名的對(duì)象*Comparator*的作用。
Function
原先我以為L(zhǎng)ambda表達(dá)式的加入只是一個(gè)簡(jiǎn)單的語(yǔ)法糖,但是后面發(fā)現(xiàn)還有更多的語(yǔ)法糖。設(shè)想一下如果你需要對(duì)一個(gè)List的數(shù)據(jù)做判斷和篩選,通常我們會(huì)按照下面這種一般做法。
public class Person { public String givenName; public String surName; public int age; public Gender gender; public String eMail; public String phone; public String address; //getters and setters //... } public class RoboContactMethods2 { public void callDrivers(List<Person> pl){ for(Person p:pl){ if (isDriver(p)){ roboCall(p); } } } public boolean isDriver(Person p){ return p.getAge() >= 16; } public void roboCall(Person p){ System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone()); } }
這樣如果有多個(gè)過(guò)濾條件的需求,就需要實(shí)現(xiàn)更多的判斷函數(shù),那么更文藝一些的做法是這樣的(還是用上面的Person對(duì)象舉例):
public interface MyTest<T> { public boolean test(T t); } public class RoboContactAnon { public void phoneContacts(List<Person> pl, MyTest<Person> aTest){ for(Person p:pl){ if (aTest.test(p)){ roboCall(p); } } } public void roboCall(Person p){ System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone()); } public static void main (String[] args) { //get PersonList for testing. List<Person> pl = getInitedPersonList(); RoboContactAnon rca = new RoboContactAnon(); rca.phoneContacts(pl, (p) -> p.getAge() > 16); } }
我們這里使用了一個(gè)自定義的*MyTest*接口,但是其實(shí)我們不需要自己定義這個(gè)接口,因?yàn)樵贘ava SE 8中,JDK為我們提供了一系列的接口供我們使用,比如我們的*MyTest*接口就可以用系統(tǒng)提供的*Predicte*接口進(jìn)行替代,它的定義跟MyTest類似:
public interface Predicate<T> { public boolean test(T t); }
除了Predicte,JDK還提供了一系列的接口供我們?cè)诓煌膱?chǎng)景使用。它們?cè)?em>java.util.function包中。下面是列舉的是JDK提供的一部分接口:
- Predicate: A property of the object passed as argument - Consumer: An action to be performed with the object passed as argument - Function: Transform a T to a U - Supplier: Provide an instance of a T (such as a factory) - UnaryOperator: A unary operator from T -> T - BinaryOperator: A binary operator from (T, T) -> T
Collections
除了上面提到的語(yǔ)法糖,和java.util.function包以外,Java SE 8還增加了java.util.stream,這是對(duì)Collections對(duì)象起到了一定的增強(qiáng)。考慮以下場(chǎng)景“你需要對(duì)一個(gè)List根據(jù)一定的條件對(duì)元素進(jìn)行過(guò)濾,然后求過(guò)濾后元素某個(gè)屬性的平均值”。我們的做法一般是這樣:
//仍舊以List<Person>舉例 double sum = 0; int count = 0; for (Person p : personList) { if (p.getAge() > 0) { sum += p.getAge(); cout++; } } double average = count > 0 ? sum/average : 0;
如果我們使用stream的話,就可以用更文藝一點(diǎn)的寫法:
// Get average of ages OptionalDouble averageAge = pl .stream() .filter((p) -> p.getAge() > 16) .mapToDouble(p -> p.getAge()) .average();
可以看到這樣寫的話代碼確實(shí)更簡(jiǎn)潔了,它把List轉(zhuǎn)換成一個(gè)Stream,然后對(duì)元素進(jìn)行操作。而且如果我們做的操作對(duì)元素的順序沒(méi)有要求那么我們可以將stream()方法換成parallelStream()方法,這樣可以得到一個(gè)可以并行處理的流,當(dāng)我們對(duì)元素進(jìn)行處理的時(shí)候,JVM會(huì)把這個(gè)流進(jìn)行劃分,對(duì)每一個(gè)部分并行的進(jìn)行處理,然后再進(jìn)行歸并,這樣可以提高處理的效率,而這些對(duì)開發(fā)人員是透明的。
劃分,映射,歸并,這些聽(tīng)起來(lái)有沒(méi)有覺(jué)得很熟悉,對(duì),這就是MapReduce,只是跟Hadoop用機(jī)器作為處理節(jié)點(diǎn)不一樣的是這里對(duì)于劃分的處理是在一個(gè)JVM里面進(jìn)行的。在java.util.stream中給我們提供了一個(gè)通用的reduce()方法:
<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner); int sumOfWeights = widgets.stream() .reduce(0, (sum, b) -> sum + b.getWeight()), Integer::sum);
其中identity是一個(gè)reduce的初始值,如果沒(méi)有元素進(jìn)行reduce的話則返回identitiy值,如果有元素進(jìn)行reduce則在identity上進(jìn)行累加。accumulator是一個(gè)累加器,負(fù)責(zé)把部分結(jié)果與另一個(gè)元素進(jìn)行累加,combiner則是一個(gè)合并器,負(fù)責(zé)把不同部分的子結(jié)果進(jìn)行合并,取得最終的結(jié)果。這里如果所進(jìn)行的運(yùn)算對(duì)元素的元素沒(méi)有要求的話我們可以使用parallelStream(),取得一個(gè)并行的流,這樣才能對(duì)流進(jìn)行劃分和并行處理,充分發(fā)揮這些新特性的性能。
將一個(gè)輸入的collection做轉(zhuǎn)換也是可以的比如下面的例子就返回了元素中某個(gè)屬性的List:
<R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner); ArrayList<String> strings = stream.collect(() -> new ArrayList<>(), (c, e) -> c.add(e.toString()), (c1, c2) -> c1.addAll(c2)); List<String> strings = stream.map(Object::toString) .collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
有可能你覺(jué)得這不是KV對(duì)操作,不像MapReduce,那么你可以將結(jié)果映射成一個(gè)Map,這就是妥妥的KV對(duì)了,這個(gè)操作需要使用到groupingBy(Collection collection)方法:
Collector<Employee, ?, Integer> summingSalaries = Collectors.summingInt(Employee::getSalary); Map<Department, Integer> salariesByDept = employees.stream().collect(Collectors.groupingBy(Employee::getDepartment, summingSalaries));
以上只是對(duì)于stream的簡(jiǎn)單舉例,詳情請(qǐng)閱讀[JSR335-FINAL]中關(guān)于java.util.stream的部分。
后記
起初我認(rèn)為這些新特性只是一些語(yǔ)法糖,現(xiàn)在我也還認(rèn)為這個(gè)特性是個(gè)語(yǔ)法糖。雖然在開發(fā)過(guò)程中很少用到這個(gè)新特性(甚至都不會(huì)用到),但是了解這些新特性總是沒(méi)有壞除的,恰當(dāng)?shù)氖褂眠@些新特性在某些場(chǎng)景下的確可以取得很好的效果(簡(jiǎn)潔的代碼,優(yōu)秀的性能)。這篇文章的初衷一是對(duì)自己所得的記錄,二是做一個(gè)分享。寫得不好的或者謬誤的地方還請(qǐng)大家批評(píng)指正,一起交流,共同進(jìn)步。
參考文獻(xiàn)
Lambda演算-wikipedia
JSR335-FINAL(Java Specification Requests)
Oracle-Java SE 8: Lambda Quick Start
My Github
|
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注