|
本文主要講到的內(nèi)容有:
- 一- 前言
- 二- 背景
- 三- lambda表達(dá)式的語(yǔ)法
- 四- Lambda程序例子
- 4-1 Runnable Lambda
- 4-2 Comparator Lambda
- 4-3 Listener Lambda
- 五- 使用Lambda表達(dá)式重構(gòu)代碼
- 5-1 Person Class
- 5-2 第一個(gè)實(shí)現(xiàn)最初的實(shí)現(xiàn)
- 5-3 重構(gòu)后的方法
- 5-4 匿名內(nèi)部類
- 5-5 Lambda Expressions
- 六- 新的javautilfunction 包
- 6-1 東方的名字書寫方式和方法引用
- 6-2 最初的實(shí)現(xiàn)
- 6-3 Function接口
- 七- Lambda表達(dá)式和java集合
- 7-1Chaining 和 Filters
- 7-2 變懶一點(diǎn)
- 7-3 stream 方法
- 7-4 改變和結(jié)果
- 7-5 使用map來(lái)計(jì)算
- 八- 總結(jié)
一- 前言
java中l(wèi)ambda表達(dá)式是java匿名函數(shù)的一種表達(dá)形式,只是寫法和我們之前學(xué)習(xí)了解到的匿名函數(shù)有點(diǎn)不一樣。本教程主要介紹Java SE 8才引進(jìn)來(lái)的lambda表達(dá)式。
花費(fèi)時(shí)間:大概1小時(shí)就可以閱讀完這篇內(nèi)容。
Lambda表達(dá)式是Java SE 8才引進(jìn)的新特性。對(duì)于只申明一個(gè)函數(shù)的接口,它提供了一個(gè)簡(jiǎn)單和簡(jiǎn)潔的方式讓程序員編寫匿名函數(shù),同時(shí)改善了Java集合框架庫(kù)(collection),使得更加容易迭代、過(guò)濾一個(gè)集合,更加容易從另一個(gè)集合中提取數(shù)據(jù)。并且在多核計(jì)算機(jī)的情況下,新特性提高了運(yùn)算性能。
平臺(tái)需求:本文所示代碼需要在jdk 8或以上平臺(tái)上面運(yùn)行。
二- 背景
java的匿名內(nèi)部類允許我們通過(guò)簡(jiǎn)潔的方式實(shí)現(xiàn)一個(gè)類或者一個(gè)類的方法,它沒(méi)有名字,只使用一次。比如下面的代碼:
JButton testButton = new JButton("Test Button");testButton.addActionListener(new ActionListener(){@Override public void actionPerformed(ActionEvent ae){System.out.println("Click Detected by Anon Class");}
});
上面的例子表示一個(gè)button增加一個(gè)監(jiān)聽(tīng)器。而ActionListener是一個(gè)接口,并且它只有一個(gè)方法。它的定義如下所示:
package java.awt.event;
import java.util.EventListener;public interface ActionListener extends EventListener {public void actionPerformed(ActionEvent e);
}
注意:上面這種接口類型有一個(gè)特點(diǎn),那就是一個(gè)接口中只定義了一個(gè)方法。這一點(diǎn)對(duì)于lambda表達(dá)式特別重要。
三- lambda表達(dá)式的語(yǔ)法
一個(gè)Lambda表達(dá)式由三部分組成:
| 參數(shù)列表 | 箭頭 | Body |
|---|
(int x, int y) | -> | x + y |
body部分可以是單個(gè)表達(dá)式,也可以是一個(gè)代碼塊。所以body相當(dāng)于匿名內(nèi)部類中的方法體。如果body中只有一個(gè)表達(dá)式,比如上面所示的x+y,那么相當(dāng)于return x+y,return是可以被省略的。
看下面例子:
(int x, int y) -> x + y() -> 42(String s) -> { System.out.println(s); }
第一個(gè)表達(dá)式有兩個(gè)形參,分別是x和y,函數(shù)的作用是返回x+y的值。
第二個(gè)表達(dá)式?jīng)]有參數(shù),函數(shù)的作用是直接返回42.
第三個(gè)表達(dá)式有一個(gè)字符串類型的參數(shù),函數(shù)的作用是打印該字符串。
了解上面的語(yǔ)法知識(shí)之后,我們?cè)賮?lái)看看其他一些例子:
四- Lambda程序例子
4-1 Runnable Lambda
我們可以使用Lambda表達(dá)式寫一個(gè)Runnable測(cè)試程序:
/*** Created by liangyh on 2016-11-27.*/
public class RunnableTest {public static void main(String[] args) {//匿名內(nèi)部類Runnable r1 = new Runnable() {@Overridepublic void run() {System.out.println("hello Runnable 1!");}};//LambdaRunnable r2 = ()-> System.out.println("hello Runnable 2");r1.run();r2.run();}
}
上面這兩個(gè)例子中,Runnable類只有一個(gè)方法(這個(gè)很重要),run方法沒(méi)有參數(shù)并且沒(méi)有返回值。使用匿名內(nèi)部類使用了五行代碼,而使用lambda只需一行代碼。
4-2 Comparator Lambda
下面是java.util.Comparator的例子:
enum Gender { MALE, FEMALE }public class Person {private String givenName;private String surName;private int age;private Gender gender;private String eMail;private String phone;private String address;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;/*** Created by liangyh on 2016-11-27.*/
public class ComparatorTest {public static void main(String[] args) {List<Person> personList = Person.createShortList();//匿名內(nèi)部類Collections.sort(personList, new Comparator<Person>() {@Overridepublic int compare(Person o1, Person o2) {return o1.getSurName().compareTo(o2.getSurName());}});for(Person p: personList){p.printName();}//lambda 1Collections.sort(personList, (Person o1, Person o2)->o1.getSurName().compareTo(o2.getSurName()));for(Person p: personList){p.printName();}//lambda 2Collections.sort(personList, (o1, o2)->o1.getSurName().compareTo(o2.getSurName()));for(Person p: personList){p.printName();}}
}
從上面的lambda 1和lambda 2中的參數(shù)可以看到,我們傳入的o1, o2可以不用指定它的類型,編譯器能夠自動(dòng)判斷。(為什么?java.lang.Comparator接口只有一個(gè)方法)
4-3 Listener Lambda
最后,我們?cè)倏匆幌翧ctionListenter的例子:
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;/*** Created by liangyh on 2016-11-27.*/
public class ListenerTest {public static void main(String[] args) {JButton testButton = new JButton("button");testButton.addActionListener(new ActionListener() {@Overridepublic void actionPerformed(ActionEvent e) {System.out.println("click button, 匿名內(nèi)部類");}});testButton.addActionListener(event -> System.out.println("click button, lambda"));JFrame frame = new JFrame("test");frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);frame.add(testButton, BorderLayout.CENTER);frame.pack();frame.setVisible(true);}
}
通過(guò)上面的程序我們看到,lambda表達(dá)式作為一個(gè)參數(shù)傳進(jìn)方法中。
五- 使用Lambda表達(dá)式重構(gòu)代碼
這一小節(jié)的內(nèi)容是在上一小節(jié)的基礎(chǔ)之上編寫的。在這一節(jié)中將給大家展示如何使用lambda表達(dá)式改善(重構(gòu))我們的代碼,使我們的代碼更加簡(jiǎn)單易懂,減少重復(fù)代碼。
下面我們將通過(guò)一步步編寫一個(gè)小項(xiàng)目的形式來(lái)展示lambda表達(dá)式的優(yōu)勢(shì)。
這個(gè)小項(xiàng)目會(huì)涉及到三類人:
- Drivers:(司機(jī))年齡>16
- Draftees:(士兵)年齡在18到25之間
- Pilots:(飛行員)年齡在23在65之間
現(xiàn)在我們有一份名單,名單里面有著三類人的相關(guān)信息,比如姓名、年齡、手機(jī)號(hào)、郵件號(hào)碼、郵件地址等等,具體的定義看一參見(jiàn)Person類。我們的任務(wù)是給所有的司機(jī)打電話,給所有的士兵發(fā)郵件,給所有的飛行員送郵寄。(搞笑得很:))
好,上面便是我們這個(gè)小項(xiàng)目簡(jiǎn)單的需求。
5-1 Person Class
下面是這三類人的定義:
enum Gender { MALE, FEMALE }public class Person {private String givenName;private String surName;private int age;private Gender gender;private String eMail;private String phone;private String address;
在Person類中有一個(gè)內(nèi)部類Builder,用來(lái)幫助構(gòu)造初始化一個(gè)Person對(duì)象。下面是創(chuàng)建一組Person對(duì)象的代碼片。完整的源碼在本文底部以鏈接的形式給出
public static List<Person> createShortList(){List<Person> people = new ArrayList<>();people.add(new Person.Builder().givenName("Bob").surName("Baker").age(21).gender(Gender.MALE).email("bob.baker@example.com").phoneNumber("201-121-4678").address("44 4th St, Smallville, KS 12333").build());people.add(new Person.Builder().givenName("Jane").surName("Doe").age(25).gender(Gender.FEMALE).email("jane.doe@example.com").phoneNumber("202-123-4678").address("33 3rd St, Smallville, KS 12333").build());people.add(new Person.Builder().givenName("John").surName("Doe").age(25).gender(Gender.MALE).email("john.doe@example.com").phoneNumber("202-123-4678").address("33 3rd St, Smallville, KS 12333").build());people.add(new Person.Builder().givenName("James").surName("Johnson").age(45).gender(Gender.MALE).email("james.johnson@example.com").phoneNumber("333-456-1233").address("201 2nd St, New York, NY 12111").build());//。。。。。。return people;}
5-2 第一個(gè)實(shí)現(xiàn)(最初的實(shí)現(xiàn)):
根據(jù)Person類和相關(guān)的選擇策略,實(shí)現(xiàn)給所有的司機(jī)打電話,給所有的士兵發(fā)郵件,給所有的飛行員送郵寄的功能。
import java.util.List;/*** Created by liangyh on 2016-11-27.*/
public class RobotContact {public void callDrivers(List<Person> plist){for(Person p :plist){if(p.getAge() >= 16){robotCall(p);}}}public void emailDraftees(List<Person> plist){for(Person p: plist){if (p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE){robotEmail(p);}}}public void mailPilots(List<Person> plist){for(Person p: plist){if (p.getAge() >= 23 && p.getAge() <= 65){robotMail(p);}}}public void robotCall(Person p){System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone());}public void robotEmail(Person p){System.out.println("EMailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getEmail());}public void robotMail(Person p){System.out.println("Mailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getAddress());}
}
上面的代碼有什么缺點(diǎn)呢?
5-3 重構(gòu)后的方法
如何完善上面的代碼呢?我們可以從判斷策略入手。如果把判斷策略放在單獨(dú)的方法里面,代碼的靈活性會(huì)好一點(diǎn)。
import java.util.List;/*** Created by liangyh on 2016-11-27.*/
public class RobotContact {public void callDrivers(List<Person> plist){for(Person p :plist){if(isDriver(p)){robotCall(p);}}}public void emailDraftees(List<Person> plist){for(Person p: plist){if (isDraftee(p)){robotEmail(p);}}}public void mailPilots(List<Person> plist){for(Person p: plist){if (isPilot(p)){robotMail(p);}}}public boolean isDriver(Person p){return p.getAge() >= 16;}public boolean isDraftee(Person p){return p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE;}public boolean isPilot(Person p){return p.getAge() >= 23 && p.getAge() <= 65;}public void robotCall(Person p){System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone());}public void robotEmail(Person p){System.out.println("EMailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getEmail());}public void robotMail(Person p){System.out.println("Mailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getAddress());}
}
上面的代碼中,我們把判斷策略提取出來(lái)分別放在單獨(dú)的函數(shù)里面。這樣判斷條件就可以重用,當(dāng)判斷條件改變的時(shí)候修改起來(lái)也很方便。
但是,這樣之后增加了許多(三個(gè))單獨(dú)的函數(shù)和許多代碼量,有沒(méi)有一種更好的方式把這三個(gè)方法合成一個(gè)呢?
5-4 匿名內(nèi)部類
在lambda表達(dá)式之前,我們可以考慮使用匿名內(nèi)部類。一個(gè)只有一個(gè)方法(比如test),方法的返回值為boolean的接口(比如MyTest.java)可以幫助我們解決問(wèn)題。在調(diào)用test函數(shù)的時(shí)候,我們可以把判斷條件傳進(jìn)這個(gè)test函數(shù)中。接口代碼如下:
/*** Created by liangyh on 2016-11-27.*/
public interface MyTest<T> {boolean test(T t);
}
所以RobotContact類變成下面的樣子:
package test;import test.Person;import java.util.List;/*** Created by liangyh on 2016-11-27.*/
public class RobotContactAnon {//注意:這里由phoneDrivers變成了phoneContacts//因?yàn)閏all的人群范圍由所傳入的參數(shù)來(lái)決定。//所以這個(gè)函數(shù)的使用范圍變廣了。public void phoneContacts(List<Person> plist, MyTest<Person> aTest){for(Person p :plist){if(aTest.test(p)){robotCall(p);}}}public void emailContacts(List<Person> plist, MyTest<Person> aTest){for(Person p: plist){if(aTest.test(p)){robotEmail(p);}}}public void mailContacts(List<Person> plist, MyTest<Person> aTest){for(Person p: plist){if(aTest.test(p)){robotMail(p);}}}public void robotCall(Person p){System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone());}public void robotEmail(Person p){System.out.println("EMailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getEmail());}public void robotMail(Person p){System.out.println("Mailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getAddress());}
}
這樣子的話我們只需要三個(gè)函數(shù)就可以了。感覺(jué)特別棒,但是在調(diào)用函數(shù)的時(shí)候就有點(diǎn)糟糕了。下面是測(cè)試函數(shù):
package test;import test.Gender;
import test.Person;import java.util.List;/*** Created by liangyh on 2016-11-27.*/
public class RobotCallTest03 {public static void main(String[] args) {List<Person> personList = Person.createShortList();RobotContactAnon robot = new RobotContactAnon();System.out.println("calling all drivers");robot.phoneContacts(personList,new MyTest<Person>() {@Overridepublic boolean test(Person person) {return person.getAge() >= 16;}});System.out.println("emailing all draftees");robot.emailContacts(personList,new MyTest<Person>() {@Overridepublic boolean test(Person p) {return p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE;}});System.out.println("mailing all pilots");robot.mailContacts(personList, new MyTest<Person>() {@Overridepublic boolean test(Person p) {return p.getAge() >= 23 && p.getAge() <= 65;}});}
}
上面的代碼我們使用到了匿名內(nèi)部類。通過(guò)匿名內(nèi)部類來(lái)重構(gòu),使得我們的邏輯代碼更加簡(jiǎn)潔清晰。無(wú)論如何,現(xiàn)在我們的代碼相比重構(gòu)之前得到了很大的改善。下面我們使用lambda表達(dá)式來(lái)代替匿名內(nèi)部類:
5-5 Lambda Expressions
package test;import java.util.List;/*** Created by liangyh on 2016-11-27.*/
public class RobotCallLambdaTest03 {public static void main(String[] args) {List<Person> personList = Person.createShortList();RobotContactAnon robot = new RobotContactAnon();System.out.println("calling all drivers");robot.phoneContacts(personList, person->person.getAge() >= 16);System.out.println("emailing all draftees");robot.emailContacts(personList,p -> p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE);System.out.println("mailing all pilots");robot.mailContacts(personList, p -> p.getAge() >= 23 && p.getAge() <= 65);}
}
是不是比使用匿名內(nèi)部類簡(jiǎn)潔好看了很多?看上面代碼的時(shí)候注意不要看暈了哈,lambda表達(dá)式只有參數(shù)和body,沒(méi)有方法名的,因?yàn)榉椒菦](méi)有必要的。編譯器自動(dòng)識(shí)別參數(shù)的類型。
java.util.function
上面的代碼我們使用到了一個(gè)MyTest接口,它的方法作為條件判斷使用。其實(shí)在jdk 8已經(jīng)為我們提供好了這樣的方法,在java.util.function包中,接口名為Predicate,定義為:
@FunctionalInterface
public interface Predicate<T> {/*** Evaluates this predicate on the given argument.** @param t the input argument* @return {@code true} if the input argument matches the predicate,* otherwise {@code false}*/boolean test(T t);
使用上面這個(gè)Predicate接口之后,我們的邏輯代碼變成下面的了:(只需把MyTest改為Predicate)
package test;import java.util.List;
import java.util.function.Predicate;/*** Created by liangyh on 2016-11-27.*/
public class RobotContactLambda {//注意:這里由phoneDrivers變成了phoneContacts//因?yàn)閏all的人群范圍由所傳入的參數(shù)來(lái)決定。//所以這個(gè)函數(shù)的使用范圍變廣了。public void phoneContacts(List<Person> plist, Predicate<Person> aTest){for(Person p :plist){if(aTest.test(p)){robotCall(p);}}}public void emailContacts(List<Person> plist, Predicate<Person> aTest){for(Person p: plist){if(aTest.test(p)){robotEmail(p);}}}public void mailContacts(List<Person> plist, Predicate<Person> aTest){for(Person p: plist){if(aTest.test(p)){robotMail(p);}}}public void robotCall(Person p){System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone());}public void robotEmail(Person p){System.out.println("EMailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getEmail());}public void robotMail(Person p){System.out.println("Mailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getAddress());}
}
相應(yīng)的測(cè)試代碼為:(其實(shí)和上一個(gè)測(cè)試代碼(main函數(shù))是一樣的,只是這里把lambda表達(dá)式提取并存到一個(gè)變量里面去了。)
package test;import java.time.Period;
import java.util.List;
import java.util.function.Predicate;/*** Created by liangyh on 2016-11-27.*/
public class RobotCallLambdaTest04 {public static void main(String[] args) {List<Person> personList = Person.createShortList();RobotContactLambda robot = new RobotContactLambda();Predicate<Person> allDrivers = person->person.getAge() >= 16;Predicate<Person> allDraftees = p -> p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE;Predicate<Person> allPilots = p -> p.getAge() >= 23 && p.getAge() <= 65;System.out.println("calling all drivers");robot.phoneContacts(personList, allDrivers);System.out.println("emailing all draftees");robot.emailContacts(personList, allDraftees);System.out.println("mailing all pilots");robot.mailContacts(personList, allPilots);}
}
是不是簡(jiǎn)潔了太多?
六- 新的java.util.function 包
jdk8不單單提供了Predicate接口,它還為我們提供了很多標(biāo)準(zhǔn)接口:
Predicate:對(duì)象的一個(gè)屬性作為參數(shù) Consumer:一個(gè)要執(zhí)行的操作和對(duì)象作為參數(shù) Function:把一個(gè)T類型的對(duì)象轉(zhuǎn)換成U類型的對(duì)象 Supplier:為類型T實(shí)例化一個(gè)對(duì)象(比如一個(gè)工廠) UnaryOperator:一元運(yùn)算符。T -> T BinaryOperator:二元運(yùn)算符。(T, T)-> T ?
6-1 東方的名字書寫方式和方法引用
對(duì)于前面的例子,如果能夠靈活的打印Person類中的相關(guān)信息,將是一件特別棒的事情。比如我希望Person類中的名字既可以以西方的方式也可以以東方的方式排版打印。在西方,名前姓后,在東方恰好相反。
6-2 最初的實(shí)現(xiàn):
public void printWesternName(){System.out.println("\nName: " + this.getGivenName() + " " + this.getSurName() + "\n" +"Age: " + this.getAge() + " " + "Gender: " + this.getGender() + "\n" +"EMail: " + this.getEmail() + "\n" +"Phone: " + this.getPhone() + "\n" +"Address: " + this.getAddress());}public void printEasternName(){System.out.println("\nName: " + this.getSurName() + " " + this.getGivenName() + "\n" +"Age: " + this.getAge() + " " + "Gender: " + this.getGender() + "\n" +"EMail: " + this.getEmail() + "\n" +"Phone: " + this.getPhone() + "\n" +"Address: " + this.getAddress());}
每一個(gè)方法打印一種。但是,如果我們希望第三種打印方式呢?那么就需要寫第三個(gè)方法。所以這樣的實(shí)現(xiàn)方式是不完美不優(yōu)雅的。
6-3 Function接口
Function接口可以很好地解決這個(gè)問(wèn)題。它只有一個(gè)方法(這個(gè)很關(guān)鍵),定義為:
public interface Function<T, R> {/*** Applies this function to the given argument.** @param t the function argument* @return the function result*/R apply(T t);
注意apply方法,參數(shù)是T類型的,返回值是R類型的。
public String printCustom(Function<Person, String> f){return f.apply(this);
}
我們?cè)趐erson類中定義了上述方法,用到了apply方法。下面,我們需要實(shí)現(xiàn)Function接口的apply方法。使用匿名內(nèi)部類或者lambda表達(dá)式:
package test2;import java.util.List;
import java.util.function.Function;/*** Created by liangyh on 2016-11-27.*/
public class NameTestNew {public static void main(String[] args) {List<Person> list = Person.createShortList();System.out.println("custom list");for(Person person: list){System.out.println(person.printCustom(p->"Name: "+p.getGivenName()+" Email: "+p.getEmail()));}Function<Person, String> westernStyle = p -> "\nName: " + p.getGivenName() + " " + p.getSurName() + "\n" +"Age: " + p.getAge() + " " + "Gender: " + p.getGender() + "\n" +"EMail: " + p.getEmail() + "\n" +"Phone: " + p.getPhone() + "\n" +"Address: " + p.getAddress();Function<Person, String> easternStyle = p -> "\nName: " + p.getSurName() + " " + p.getGivenName() + "\n" +"Age: " + p.getAge() + " " + "Gender: " + p.getGender() + "\n" +"EMail: " + p.getEmail() + "\n" +"Phone: " + p.getPhone() + "\n" +"Address: " + p.getAddress();System.out.println("western ");for(Person person: list){System.out.println(person.printCustom(westernStyle));}System.out.println("eastern ");for (Person person: list){System.out.println(person.printCustom(westernStyle));}}
}
上面代碼中的變量westernStyle和easternStyle,其實(shí)就是相應(yīng)接口的實(shí)現(xiàn)類的對(duì)象的句柄或者說(shuō)引用。
上面程序的執(zhí)行結(jié)果為:
custom list
Name: Bob Email: bob.baker@example.com
Name: Jane Email: jane.doe@example.com
Name: John Email: john.doe@example.com
Name: James Email: james.johnson@example.com
Name: Joe Email: joebob.bailey@example.com
Name: Phil Email: phil.smith@examp;e.com
Name: Betty Email: betty.jones@example.com
western Name: Bob Baker
Age: 21 Gender: MALE
EMail: bob.baker@example.com
Phone: 201-121-4678
Address: 44 4th St, Smallville, KS 12333Name: Jane Doe
Age: 25 Gender: FEMALE
EMail: jane.doe@example.com
Phone: 202-123-4678
Address: 33 3rd St, Smallville, KS 12333Name: John Doe
Age: 25 Gender: MALE
EMail: john.doe@example.com
Phone: 202-123-4678
Address: 33 3rd St, Smallville, KS 12333Name: James Johnson
Age: 45 Gender: MALE
EMail: james.johnson@example.com
Phone: 333-456-1233
Address: 201 2nd St, New York, NY 12111Name: Joe Bailey
Age: 67 Gender: MALE
EMail: joebob.bailey@example.com
Phone: 112-111-1111
Address: 111 1st St, Town, CA 11111Name: Phil Smith
Age: 55 Gender: MALE
EMail: phil.smith@examp;e.com
Phone: 222-33-1234
Address: 22 2nd St, New Park, CO 222333Name: Betty Jones
Age: 85 Gender: FEMALE
EMail: betty.jones@example.com
Phone: 211-33-1234
Address: 22 4th St, New Park, CO 222333
eastern Name: Bob Baker
Age: 21 Gender: MALE
EMail: bob.baker@example.com
Phone: 201-121-4678
Address: 44 4th St, Smallville, KS 12333Name: Jane Doe
Age: 25 Gender: FEMALE
EMail: jane.doe@example.com
Phone: 202-123-4678
Address: 33 3rd St, Smallville, KS 12333Name: John Doe
Age: 25 Gender: MALE
EMail: john.doe@example.com
Phone: 202-123-4678
Address: 33 3rd St, Smallville, KS 12333Name: James Johnson
Age: 45 Gender: MALE
EMail: james.johnson@example.com
Phone: 333-456-1233
Address: 201 2nd St, New York, NY 12111Name: Joe Bailey
Age: 67 Gender: MALE
EMail: joebob.bailey@example.com
Phone: 112-111-1111
Address: 111 1st St, Town, CA 11111Name: Phil Smith
Age: 55 Gender: MALE
EMail: phil.smith@examp;e.com
Phone: 222-33-1234
Address: 22 2nd St, New Park, CO 222333Name: Betty Jones
Age: 85 Gender: FEMALE
EMail: betty.jones@example.com
Phone: 211-33-1234
Address: 22 4th St, New Park, CO 222333
七- Lambda表達(dá)式和java集合
上一節(jié)介紹了Function接口和lambda表達(dá)式的基本語(yǔ)法。這一節(jié)將介紹lambda表達(dá)式如何改善Collection框架。
現(xiàn)在我們的小項(xiàng)目中的drivers、pilots和draftees的搜索判斷條件封裝在SearchCriteria類中。代碼如下所示:
package test2;import test.*;import java.util.HashMap;
import java.util.Map;
import java.util.function.Predicate;/*** Created by liangyh on 2016-11-27.*/
public class SearchCriteria {private final Map<String, Predicate<Person>> searchMap = new HashMap<>();private SearchCriteria(){super();initSearchMap();}private void initSearchMap(){Predicate<test2.Person> allDrivers = person->person.getAge() >= 16;Predicate<test2.Person> allDraftees = p -> p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE;Predicate<test2.Person> allPilots = p -> p.getAge() >= 23 && p.getAge() <= 65;searchMap.put("allDrivers", allDrivers);searchMap.put("allDraftees", allDraftees);searchMap.put("allPilots", allPilots);}public Predicate<Person> getCriteria(String predicateName){Predicate<Person> target;target = searchMap.get(predicateName);if(target == null){System.out.println("search criteria not found");System.exit(1);}return target;}public static SearchCriteria getInstance(){return new SearchCriteria();}
}
第一個(gè)測(cè)試:(先看測(cè)試代碼,后面有詳細(xì)解釋)
package test2;import java.util.List;/*** Created by liangyh on 2016-11-27.*/
public class Test01ForEach {public static void main(String[] args) {List<Person> personList = Person.createShortList();System.out.println("\nwestern phone list");personList.forEach(p->p.printWesternName());System.out.println("\neastern phone list");personList.forEach(Person::printEasternName);System.out.println("\ncustom phone list");personList.forEach(p -> {System.out.println(p.printCustom(r-> "name: "+r.getGivenName()+" EMail:"+r.getEmail()));});}
}
上面代碼中的forEach方法是從jdk8才開(kāi)始出現(xiàn)的。這個(gè)方法的定義為:
/*** Performs the given action for each element of the {@code Iterable}* until all elements have been processed or the action throws an* exception. Unless otherwise specified by the implementing class,* actions are performed in the order of iteration (if an iteration order* is specified). Exceptions thrown by the action are relayed to the* caller.** @implSpec* <p>The default implementation behaves as if:* <pre>{@code* for (T t : this)* action.accept(t);* }</pre>** @param action The action to be performed for each element* @throws NullPointerException if the specified action is null* @since 1.8*/default void forEach(Consumer<? super T> action) {Objects.requireNonNull(action);for (T t : this) {action.accept(t);}}
forEach方法調(diào)用了T.accept()方法,而accept方法定義在java.util.function包中,它表示把一個(gè)操作(或者行為)作為函數(shù)參數(shù)傳進(jìn)去。好,知道它的來(lái)源之后,明白了它是一個(gè)未實(shí)現(xiàn)的方法,所以我們可以采用匿名內(nèi)部類或者lambda表達(dá)式實(shí)現(xiàn)它。上面的測(cè)試代碼main函數(shù)就是。下面便是accept方法的定義。
@FunctionalInterface
public interface Consumer<T> {/*** Performs this operation on the given argument.** @param t the input argument*/void accept(T t);
我們可以通過(guò)上面的方式遍歷所有的集合。它的基本結(jié)構(gòu)和for循環(huán)類似。但是,我們這里的遍歷方式提供了很多種好處:
7-1、Chaining 和 Filters
并且在jdk1.8中,在循環(huán)迭代一個(gè)集合的時(shí)候我們可以把不同的方法鏈接在一起。
下面的例子是先過(guò)濾再循環(huán)一個(gè)List。
import java.util.List;/*** Created by liangyh on 2016-11-27.*/
public class Test02Filter {public static void main(String[] args) {List<Person> personList = Person.createShortList();SearchCriteria search = SearchCriteria.getInstance();System.out.println("western pilot phone list");//兩個(gè)冒號(hào)表示方法引用,method reference,personList.stream().filter(search.getCriteria("allPilots")).forEach(Person::printWesternName);//或者/*personList.stream().filter(search.getCriteria("allPilots")).forEach(p->p.printWesternName());*/System.out.println("\n eastern draftee phone list");personList.stream().filter(search.getCriteria("addDraftees")).forEach(Person::printEasternName);}
}
第一個(gè)循環(huán)過(guò)濾條件為所有的飛行員,第二個(gè)為所有的士兵。
一部分運(yùn)行結(jié)果為:
eastern draftee phone listName: Baker Bob
Age: 21 Gender: MALE
EMail: bob.baker@example.com
Phone: 201-121-4678
Address: 44 4th St, Smallville, KS 12333Name: Doe John
Age: 25 Gender: MALE
EMail: john.doe@example.com
Phone: 202-123-4678
Address: 33 3rd St, Smallville, KS 12333
7-2 變懶一點(diǎn)
上面代碼所展示的這些特性是很有用的,但是為什么在for循環(huán)已經(jīng)很完美的情況下還要增加它們呢?通過(guò)把迭代移到j(luò)ava集合庫(kù)中,使得程序員能夠更好地優(yōu)化他的代碼。為了進(jìn)一步解釋,得介紹兩個(gè)專有名詞:
- 懶(Laziness):在程序中,laziness指的是我們?cè)谟玫侥硨?duì)象的時(shí)候才去初始化和執(zhí)行它。在前面的例子中,最后那個(gè)循環(huán)是“懶”的,因?yàn)檠h(huán)是在過(guò)濾之后的結(jié)果集合中發(fā)生的。過(guò)濾之后只剩下兩個(gè)士兵了,所以遍歷也就只發(fā)生在這兩個(gè)士兵上,而不是list中所有的人。
- 急(Eagerness):如果for循環(huán)未經(jīng)過(guò)濾遍歷了所有的list中的元素,那么就是這里說(shuō)的eagerness。
通過(guò)把“循環(huán)”作為java 集合框架庫(kù)的一部分(之前是通過(guò)迭代iterator的方式),使得程序員能夠通過(guò)懶的方式優(yōu)化代碼。
7-3 stream 方法
在上面的例子中,細(xì)心的同學(xué)已經(jīng)注意到stream方法在過(guò)濾和循環(huán)之前調(diào)用。這個(gè)方法把java中的集合(Collection)作為輸入,把java.util.stream.Stream接口作為輸出。一個(gè)Stream代表著可以鏈接不同方法的一系列元素。一個(gè)鏈接操作(chain)只能在特定的Stream中發(fā)生一次。所調(diào)用的方法決定了這個(gè)stream是串行(默認(rèn))的還是并行的。本小節(jié)末尾會(huì)給出一個(gè)并行stream的例子。
7-4 改變和結(jié)果
前面已經(jīng)提到,一個(gè)stream只能使用一次,使用之后就會(huì)被拋棄。所以,集合中的元素進(jìn)行stream操作的時(shí)候不能被修改。但是,如果你想在鏈接操作(chain)之后能夠返回剛才集合中相關(guān)元素呢?你可以把這些元素存到一個(gè)新的集合當(dāng)中。具體怎么做?
package test2;import java.util.List;
import java.util.stream.Collectors;/*** Created by liangyh on 2016-11-28.*/
public class Test03toList {public static void main(String[] args) {List<Person> personList = Person.createShortList();SearchCriteria searchCriteria = SearchCriteria.getInstance();List<Person> pilotList = personList.stream().filter(searchCriteria.getCriteria("allPilots")).collect(Collectors.toList());System.out.println("\n western pilot phone list");pilotList.forEach(Person::printWesternName);//或者//pilotList.forEach(person -> person.printWesternName());}
}
上面的collect方法里面的參數(shù)是Collectors工具類。Collectors工具類能夠更具過(guò)濾的結(jié)果返回一個(gè)List或者Set。上面的代碼展示了一個(gè)stream的結(jié)果被存到一個(gè)新的數(shù)組中然后循環(huán)打印。
7-5 使用map來(lái)計(jì)算
這里的map是指java.util.stream.Stream類中的map相關(guān)方法。
map方法經(jīng)常結(jié)合過(guò)濾使用。
下面程序是計(jì)算所有飛行員的總年齡和平均年齡。
package test2;import java.util.List;
import java.util.Optional;
import java.util.OptionalDouble;/*** Created by liangyh on 2016-11-28.*/
public class Test04Map {public static void main(String[] args) {List<Person> personList = Person.createShortList();SearchCriteria search = SearchCriteria.getInstance();System.out.println("old style");int sum = 0;int count = 0;for(Person p:personList){if(p.getAge() >= 23 && p.getAge() <= 65){sum += p.getAge();count++;}}long average = sum/count;System.out.println("total ages: "+sum);System.out.println("average age: "+average);System.out.println("\n new style");long totalAge = personList.stream().filter(search.getCriteria("allPilots")).mapToInt(p->p.getAge()).sum();OptionalDouble averageAge = personList.parallelStream().filter(search.getCriteria("allPilots")).mapToDouble(p -> p.getAge()).average();System.out.println("total ages: "+totalAge);System.out.println("average age: "+averageAge.getAsDouble());}
}
執(zhí)行結(jié)果:
old style
total ages: 150
average age: 37new style
total ages: 150
average age: 37.5
最后那個(gè)循環(huán)計(jì)算平均年齡的方法中沒(méi)有計(jì)算出sum就得出了平均數(shù)。 還注意到parallelStream方法用來(lái)獲取并行的stream。所以那些值可以并行計(jì)算。 返回的結(jié)果也有一點(diǎn)不一樣。
八- 總結(jié)
Lambda表達(dá)式讓我們程序員的對(duì)代碼的關(guān)注點(diǎn)發(fā)生了改變。和匿名函數(shù)相比,Lambda表達(dá)式減少了很多東西,比如接口名字和方法名,使得我們更加關(guān)注真實(shí)能用到的組件,比如參數(shù)和body里面的邏輯部分。這樣做的結(jié)果便是提高代碼開(kāi)發(fā)效率和閱讀體驗(yàn)。
從這邊博客中你可以了解到:
- Java匿名內(nèi)部類
- jdk se 8中l(wèi)ambda表達(dá)式代替匿名內(nèi)部類。
- lambda表達(dá)式的語(yǔ)法
- 使用java.util.function.Predicate接口作為查詢判斷條件
- java.util.function.Function接口
- jdk8增加支持lambda表達(dá)式的新特性。
源碼下載:http://download.csdn.net/detail/liangyihuai/9695471
lambda的定義 https://en./wiki/Lambda_expression
|