本文译自http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/Lambda-QuickStart/index.html#section5。读完英文原本后,获益良多,因此试着将它翻译出来,与大家共享。
概述:
1. 课程目标
这门课程介绍Java SE8新Lambda表达式
2. 完成时间
大约1小时
3. 介绍
Lambda表达式是Java SE8中新且重要的特性,它提供了一种简洁的方法,仅仅通过使用一个表达式,就可以表示只含有一个方法的接口。Lambda表达式使得Collection类库更加容易遍历、过滤和获取数据;另外,新的并发特性也提高了在多核环境的性能。
这份Oracle by Example(OBE)将介绍Java SE 8里的Lambda表达式,介绍匿名内部方法,讨论函数式接口以及lambda新语法。随后,将展示lambda表达式使用前和使用后的常见用法案例
下半章节将展示常见的搜索案例,以及Java代码如何通过lambda表达式优化,另外,一些常用的函数式接口,java.util.function里的Predictate和Function,也将在该章节展示。
OBE最后展示Java SE 8的collection类如何通过lambda表达式得到更新
所有案例的源代码都可以下载。
硬件和软件要求:
JDK 8
Netbeans 7.4
前提:
为了运行代码,你必须要有JDK 8以及Netbean 7.4或以上的版本。可以在Open JDK中找到链接,为了方便,下面是直接获取的链接
1. JDK8
2. Netbean 7.4或以上,支持JDK8
注意:Netbean可在所有主流的操作系统安装,这份OBE是在Linux Mint 13开发的。
下载Netbean和JDK,按照提示安装JDK8和Netbeans,将Netbean和Java的bin目录加入到系统的PATH中。
注意:这篇OBE最近的更新是在2013年9月
背景知识
1.匿名内部类
在Java,匿名内部类提供了一种途径来实现仅仅需要在应用程序中出现一次的类。例如,在标准的Swing或JavaFX应用程序中,一系列的event
handler需要用来处理键盘和鼠标的事件,与其写一个为每一个事件的处理类,你可以这样写:
JButton testButton = new JButton("Test Button");
testButton.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent ae) {
System.out.println("Click Detected by Anon Class");
}
});
否则,就需要为每一个事件定义一个实现了ActionListener的类。通过在需要匿名内部类的地方创建它,代码就会变得容易读一点。但代码依然不够优雅,因为花费相当一部分的代码,只为了定义一个方法。
2.函数式接口
定义ActionListener接口的代码,类似这种:
package java.awt.event;
import java.util.EventListener
public interface ActionListener extends EventListener {
public void actionPerformed(ActionEvent e);
}
ActionListener接口只有一个方法。在Java SE 8中,类似这种接口就是“函数式接口”。
注意:这种接口,之前的版本称为是“单抽象方法类型”(Single Abstract Method type)。
在Java中,匿名内部类结合函数式方法使用是一种常见的模式。除了EventListener之外,Runnable和Comparator接口也是同样的模式。因此,函数式接口用于结合lambda表达式使用。
3. Lambda表达式语法
Lambda表达式将5行代码转为一个表达式,来解决笨重的匿名内部类声明问题。这种简单水平的方案,解决了内部类的“垂直高度问题”。
一条Lambda表达式由3个部分组成
A. 参数列表 (int x , int y)
B. 箭头 ->
C. 方法体 x + y
方法体可以是一条表达式或语句块。如果是表达式,方法体只是简单计算并返回。如果是语句块,Lambda表达式的方法体就跟普通的方法体一样,return语句表示将控制权转交给调用者。Break和continue在方法体的最外层是不合法的,但可以在循环里面用。如果方法体有返回值,那么,每一条控制路径上的调用者都需要向前一个调用者返回值或着抛出异常。
下面就是Lambda表达式的几个例子:
(int x , int y) -> x + y
() -> 42
(String s) -> { System.out.println(s); }
第一个表达式输入两个整数型的参数,命名为x和y,使用表达式的形式,返回x+y。第二个表达式没有接受任何参数,使用表达式返回一个整数42。第三个表达式接受一个String类型的参数,使用代码块的形式,输出参数到控制台,无任何返回值。
关于基本的语法,请看下面的几个例子。
Lambda范例
以下是Java中Lambda的几个范例:
1. Runnable
public class RunnableTest {
public static void main(String[] args) {
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!");
// Run em!
r1.run();
r2.run();
}
}
在代码里的两个例子中,都没有传入参数和返回值。Runnable的Lambda表达式,使用代码块的形式,将5行代码转成1行表达式。
2. Comparator
在Java,Comparator类用于集合排序。在下面的例子中,一个ArrayList集合包含Person对象,将根据surName进行排序,以下是Person类的成员变量。
public class Person {
private String givenName;
private String surName;
private int age;
private Gender gender;
private String eMail;
private String phone;
private String address;
下面的代码使用匿名内部类和一对Lambda表达式实现Comparator接口。
public class ComparatorTest {
public static void main(String[] args) {
List<Person> personList = Person.createShortList();
// Sort with Inner Class
Collections.sort(personList, new Comparator<Person>(){
public int compare(Person p1, Person p2){
return p1.getSurName().compareTo(p2.getSurName());
}
});
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, (Person p1, Person p2) -> p1.getSurName().compareTo(p2.getSurName()));
for(Person p:personList){
p.printName();
}
// Print Desc
System.out.println("=== Sorted Desc SurName ===");
Collections.sort(personList, (p1, p2) -> p2.getSurName().compareTo(p1.getSurName()));
for(Person p:personList){
p.printName();
}
}
}
第32行代码很容易取代17-21行。注意到第一个lambda表达式声明了传给表达式的参数类型,然而,在第二个lambda表达式,这个类型是多余的。Lambda表达式支持从上下文推导“目标类型”的对象类型。因为Comparator的参数是单一的,我们将结果返回给Comparator,编译器就能推导出两个参数都为Person类型。
3. Listener
最后,让我们来看一下ActionListener的例子
public class ListenerTest {
public static void main(String[] args) {
JButton testButton = new JButton("Test Button");
testButton.addActionListener(new ActionListener(){
@Override public void actionPerformed(ActionEvent ae){
System.out.println("Click Detected by Anon Class");
}
});
testButton.addActionListener(e -> System.out.println("Click Detected by Lambda Listner"));
// Swing stuff
JFrame frame = new JFrame("Listener Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(testButton, BorderLayout.CENTER);
frame.pack();
frame.setVisible(true);
}
}
注意到Lambda表达式被作为一个参数传递给方法。
目标类型用于一系列的上下文推断,包括下面:
l 变量声明;
l 赋值;
l 返回声明;
l 数组初始化;
l 方法或构造器参数;
l Lambda表达式内;
l 条件表达式‘?:’;
l 转型表达式
使用Lambda表达式提升代码
这部分的内容通过前面的例子构建,展示Lambda表达式如何能够提升你的代码。Lambdas应能够提供一种更好地支持“不重复自己”(Don‘t
Repeat Yourself)的原则,使代码更加简单易读。
一个常用查询案例
一个常见的查询应用就是在一个集合对象里面根据特定的规则来查找满足条件的数据。在JavaOne 2012,由Stuart Marks和Mike
Duigou所作的著名演讲”Jump-Starting Lambda”,就是使用了这么一个使用案例来贯穿始终,给定一个人员集合,运用不同的规则匹配人员对象,使电话自动呼叫该人。我们这篇教程将在简单的案例中,展示细微的不同之处.
在这个案例里,我们需要区分在美国的三个不同人群来发送信息:
l 司机(Drivers):超过16岁
l 入伍军人(Draftees):男性,年龄在18岁至25岁之间
l 飞行员(Pilots),尤其是商业领域:年龄在23岁至65岁之间
1. Person Class
在列表中的每一个人都定义为如下属性:
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类使用一个Builder内部对象来创建新的Person对象,一列人使用createShortList方法来创建。这里是代码的一小部分。注意:该节的所有Netbean代码都可以在该部分结束的位置下载。
public static class Builder{
private String givenName="";
private String surName="";
private int age = 0;
private Gender gender = Gender.FEMALE;
private String eMail = "";
private String phone = "";
private String address = "";
public Person.Builder givenName(String givenName){
this.givenName = givenName;
return this;
}
createShortList()代码片段:
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("[email protected]mple.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("[email protected]")
.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("[email protected]")
.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("[email protected]")
.phoneNumber("333-456-1233")
.address("201 2nd St, New York, NY 12111")
.build()
);
people.add(
new Person.Builder()
.givenName("Joe")
.surName("Bailey")
.age(67)
.gender(Gender.MALE)
.email("[email protected]")
.phoneNumber("112-111-1111")
.address("111 1st St, Town, CA 11111")
.build()
);
people.add(
new Person.Builder()
.givenName("Phil")
.surName("Smith")
.age(55)
.gender(Gender.MALE)
.email("[email protected];e.com")
.phoneNumber("222-33-1234")
.address("22 2nd St, New Park, CO 222333")
.build()
);
第一次尝试
当我们定义了Person类的查询条件后,我们就可以写一个RoboContact的类。一个可行办法就是为每一个使用方式定义一个方法。
RoboContactMethods.java
public class RoboContactMethods {
public void callDrivers(List<Person> pl){
for(Person p:pl){
if (p.getAge() >= 16){
roboCall(p);
}
}
}
public void emailDraftees(List<Person> pl){
for(Person p:pl){
if (p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE){
roboEmail(p);
}
}
}
public void mailPilots(List<Person> pl){
for(Person p:pl){
if (p.getAge() >= 23 && p.getAge() <= 65){
roboMail(p);
}
}
}
public void roboCall(Person p){
System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone());
}
public void roboEmail(Person p){
System.out.println("EMailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getEmail());
}
public void roboMail(Person p){
System.out.println("Mailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getAddress());
}
}
你可以看到方法的名字callDrivers、emailDraftees和mailPilots都描述了所执行的方法。查询规则清晰传达;每一个动作都有相应的方法调用。然而,这样的设计有以下几点弊端:
l 没有遵循DRY(Do not Repeat Youself)原则。每一个方法都在执行同样的循环机制;每一个查询规则都需要在每一个方法里面重写一遍。
l 如果用户的使用方式多样,将需要大量的方法来满足。
l 代码不灵活。如果查询规则改变了,你讲需要写相当一部分代码来更新,因此,代码也不是那么好维护。
重写方法
如何解决上诉问题?一个好的方法是从查询条件入手。如果将查询条件独立成方法,那么,代码将改进。
RoboContactMethods2.java
public class RoboContactMethods2 {
public void callDrivers(List<Person> pl){
for(Person p:pl){
if (isDriver(p)){
roboCall(p);
}
}
}
public void emailDraftees(List<Person> pl){
for(Person p:pl){
if (isDraftee(p)){
roboEmail(p);
}
}
}
public void mailPilots(List<Person> pl){
for(Person p:pl){
if (isPilot(p)){
roboMail(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 roboCall(Person p){
System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone());
}
public void roboEmail(Person p){
System.out.println("EMailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getEmail());
}
public void roboMail(Person p){
System.out.println("Mailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getAddress());
}
}
查询条件被封装进方法里,这是对前面案例代码的改进。这样查询条件就可以重用,以及统一由该类来作更新。但这里依然有很多程序代码,依然需要为每一个用户使用方式顶一个独立的方法。有更好地方式来将查询条件传递给方法吗?
匿名类
在Lambda表达式之前,匿名内部类就是一个解决方法。例如,只有一个方法的接口,该方法只返回一个布尔值。当方法被调用时,相应的查询条件也被传递到方法中。接口定义如下:
public interface MyTest<T> {
public boolean test(T t);
}
上面的robot类可以更新为:
RoboContactsAnon.java
/**
*
* @author MikeW
*/
public class RoboContactAnon {
public void phoneContacts(List<Person> pl, MyTest<Person> aTest){
for(Person p:pl){
if (aTest.test(p)){
roboCall(p);
}
}
}
public void emailContacts(List<Person> pl, MyTest<Person> aTest){
for(Person p:pl){
if (aTest.test(p)){
roboEmail(p);
}
}
}
public void mailContacts(List<Person> pl, MyTest<Person> aTest){
for(Person p:pl){
if (aTest.test(p)){
roboMail(p);
}
}
}
public void roboCall(Person p){
System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone());
}
public void roboEmail(Person p){
System.out.println("EMailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getEmail());
}
public void roboMail(Person p){
System.out.println("Mailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getAddress());
}
}
这又是一个改进!因为只有三个方法需要用来进行自动式的操作。然而,这里依然有一个小问题,当方法调用时,代码将变得难读。以下是该类的测试方法:
RoboCallTest03.java
import java.util.List;
/**
* @author MikeW
*/
public class RoboCallTest03 {
public static void main(String[] args) {
List<Person> pl = Person.createShortList();
RoboContactAnon robo = new RoboContactAnon();
System.out.println("\n==== Test 03 ====");
System.out.println("\n=== Calling all Drivers ===");
robo.phoneContacts(pl,
new MyTest<Person>(){
@Override
public boolean test(Person p){
return p.getAge() >=16;
}
}
);
System.out.println("\n=== Emailing all Draftees ===");
robo.emailContacts(pl,
new MyTest<Person>(){
@Override
public boolean test(Person p){
return p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE;
}
}
);
System.out.println("\n=== Mail all Pilots ===");
robo.mailContacts(pl,
new MyTest<Person>(){
@Override
public boolean test(Person p){
return p.getAge() >= 23 && p.getAge() <= 65;
}
}
);
}
}
这是“垂直高度问题”的极好的案例。代码有些难读,另外,我们必须为每一个用户使用方式编写一个自定义的查询条件。
Lambda表达式该出场了
Lambda表达式解决了上面遇到的所有问题。但我们还是先查一下它的家底吧!
Java.util.function
在前一个例子里,函数式接口MyTest将匿名类传递进方法,然而,编写MyTest可能没有必要。Java SE 8的java.util.function包提供了一些列标准的函数式接口。在本例中,我们使用Predicate接口来满足需求。Java源代码中的Predicate接口看起来如下:
public interface Predicate<T> {
public boolean test(T t);
}
test方法接收一个泛型类型并返回一个布尔值,这就符合我们根据条件做出判断的情景。下面是robo类的最终版本。
RoboContactsLambda.java
import java.util.List;
import java.util.function.Predicate;
/**
*
* @author MikeW
*/
public class RoboContactLambda {
public void phoneContacts(List<Person> pl, Predicate<Person> pred){
for(Person p:pl){
if (pred.test(p)){
roboCall(p);
}
}
}
public void emailContacts(List<Person> pl, Predicate<Person> pred){
for(Person p:pl){
if (pred.test(p)){
roboEmail(p);
}
}
}
public void mailContacts(List<Person> pl, Predicate<Person> pred){
for(Person p:pl){
if (pred.test(p)){
roboMail(p);
}
}
}
public void roboCall(Person p){
System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone());
}
public void roboEmail(Person p){
System.out.println("EMailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getEmail());
}
public void roboMail(Person p){
System.out.println("Mailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getAddress());
}
}
仅仅只有三个方法!Lambda表达式将传入方法,选择满足条件的Person对象。
2. 解决“垂直高度”问题
Lambda表达式解决了“垂直高度”问题,并且使得重用代码变得简单,下面是使用Lambda表达式跟新的测试类。
RoboCallTest04.java
import java.util.List;
import java.util.function.Predicate;
/**
*
* @author MikeW
*/
public class RoboCallTest04 {
public static void main(String[] args){
List<Person> pl = Person.createShortList();
RoboContactLambda robo = new RoboContactLambda();
// Predicates
Predicate<Person> allDrivers = p -> p.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("\n==== Test 04 ====");
System.out.println("\n=== Calling all Drivers ===");
robo.phoneContacts(pl, allDrivers);
System.out.println("\n=== Emailing all Draftees ===");
robo.emailContacts(pl, allDraftees);
System.out.println("\n=== Mail all Pilots ===");
robo.mailContacts(pl, allPilots);
// Mix and match becomes easy
System.out.println("\n=== Mail all Draftees ===");
robo.mailContacts(pl, allDraftees);
System.out.println("\n=== Call all Pilots ===");
robo.phoneContacts(pl, allPilots);
}
}
上述代码为每一个人群创建了一个Predicate对象:allDrivers、allDraftees和allPilots。你能够将这些Predicate接口传递给任何用户使用方法。代码紧凑、易读,而且不重复。
java.util.function包
当然,Predicate接口并不是Java SE 8提供的唯一一个函数式接口。Java SE 8为开发人员设计了一系列起步级的函数式接口。
l Predicate: 传递一个对象作为参数
l Consumer: 传入的对象作为参数并执行动作
l Function: 将T转成
U
l Supplier: 提供T的一个实例
l UnaryOperator: 一元运算符,输入T,返回T
l BinaryOperator: 二元运算符,输入同类型的T,T,返回T
除此之外,还有许多针对基本类型的接口。它们将会是你编写Lambda表达式的一个很好的起点。
东方式名字及方法引用
基于前面的代码案例,如果Person类有一套灵活的打印系统,我觉得会更好。其中一个需求就是要用东方式和西方式来打印人名。在西方国家,名(firstname)在前,姓(surname)在后;而在许多东方国家,则是姓(surname)在前,名(firstname)在后。
1. 旧风格的代码
这里展示如何在没有使用Lambda表达式的代码
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());
}
需要为每一种名字显示方式定义一个方法。
Function接口
Function接口对这个问题非常有用,它仅仅只有一个apply方法,签名如下:
public R apply(T t) { }
它输入一个泛型T,返回一个泛型R。在这里例子里面,则是,输入Person类,返回String类,一个更加灵活地打印方法可以定义为如下:
Person.java
public String printCustom(Function <Person, String> f){
return f.apply(this);
}
相当简单,方法传入一个Function参数,返回一个String类型的结果。apply方法处理定义Person信息返回的lambda表达式。
Function如何定义,这里是调用前一个例子里方法的代码:
NameTestNew.java
import java.util.List;
import java.util.function.Function;
/**
* @author MikeW
*/
public class NameTestNew {
public static void main(String[] args) {
System.out.println("\n==== NameTestNew ===");
List<Person> list1 = Person.createShortList();
// Print Custom First Name and e-mail
System.out.println("===Custom List===");
for (Person person:list1){
System.out.println(
person.printCustom(p -> "Name: " + p.getGivenName() + " EMail: " + p.getEmail())
);
}
// Define Western and Eastern Lambdas
Function<Person, String> westernStyle = p -> {
return "\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();
// Print Western List
System.out.println("\n===Western List===");
for (Person person:list1){
System.out.println(
person.printCustom(westernStyle)
);
}
// Print Eastern List
System.out.println("\n===Eastern List===");
for (Person person:list1){
System.out.println(
person.printCustom(easternStyle)
);
}
}
}
第一个循环仅仅是打印名字和email地址。但一个合法的lambda表达式传递给了printCustom方法。东方式和西方式的打印代码也通过lambda表达式存储在变量里。然后将它们传入两个遍历循环中。Lambda表达式可以整合进map中,以使得复用更加容易。Lambda表达式确实提供了相当多的便利性。
案例输出
这里是上述代码的输出:
==== NameTestNew02 ===
===Custom List===
Name: Bob EMail: [email protected]
Name: Jane EMail: [email protected]
Name: John EMail: [email protected]
Name: James EMail: [email protected]
Name: Joe EMail: [email protected]
Name: Phil EMail: [email protected];e.com
Name: Betty EMail: [email protected]
===Western List===
Name: Bob Baker
Age: 21 Gender: MALE
EMail: [email protected]
Phone: 201-121-4678
Address: 44 4th St, Smallville, KS 12333
Name: Jane Doe
Age: 25 Gender: FEMALE
EMail: [email protected]
Phone: 202-123-4678
Address: 33 3rd St, Smallville, KS 12333
Name: John Doe
Age: 25 Gender: MALE
EMail: [email protected]
Phone: 202-123-4678
Address: 33 3rd St, Smallville, KS 12333
Name: James Johnson
Age: 45 Gender: MALE
EMail: [email protected]
Phone: 333-456-1233
Address: 201 2nd St, New York, NY 12111
Name: Joe Bailey
Age: 67 Gender: MALE
EMail: [email protected]
Phone: 112-111-1111
Address: 111 1st St, Town, CA 11111
Name: Phil Smith
Age: 55 Gender: MALE
EMail: [email protected];e.com
Phone: 222-33-1234
Address: 22 2nd St, New Park, CO 222333
Name: Betty Jones
Age: 85 Gender: FEMALE
EMail: [email protected]
Phone: 211-33-1234
Address: 22 4th St, New Park, CO 222333
===Eastern List===
Name: Baker Bob
Age: 21 Gender: MALE
EMail: [email protected]
Phone: 201-121-4678
Address: 44 4th St, Smallville, KS 12333
Name: Doe Jane
Age: 25 Gender: FEMALE
EMail: [email protected]
Phone: 202-123-4678
Address: 33 3rd St, Smallville, KS 12333
Name: Doe John
Age: 25 Gender: MALE
EMail: [email protected]
Phone: 202-123-4678
Address: 33 3rd St, Smallville, KS 12333
Name: Johnson James
Age: 45 Gender: MALE
EMail: [email protected]
Phone: 333-456-1233
Address: 201 2nd St, New York, NY 12111
Name: Bailey Joe
Age: 67 Gender: MALE
EMail: [email protected]
Phone: 112-111-1111
Address: 111 1st St, Town, CA 11111
Name: Smith Phil
Age: 55 Gender: MALE
EMail: [email protected];e.com
Phone: 222-33-1234
Address: 22 2nd St, New Park, CO 222333
Name: Jones Betty
Age: 85 Gender: FEMALE
EMail: [email protected]
Phone: 211-33-1234
Address: 22 4th St, New Park, CO 222333
Lambda表达式和Collections
前一部分介绍了Function接口,以基本的Lambda表达式的例子结束。这节将展示lambda表达式如何提升Collections类。
在迄今为止出现的、的所有例子当中,集合类使用得相对较少。然而,一系列lambda表达式的新特性改变了集合的使用方法。本节主要讲述新特性的其中一小部分。
1. 类的加法
所有人群的查询条件都封装在SearchCriteria类中。
public class SearchCriteria {
private final Map<String, Predicate<Person>> searchMap = new HashMap<>();
private SearchCriteria() {
super();
initSearchMap();
}
private void initSearchMap() {
Predicate<Person> allDrivers = p -> p.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;
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();
}
}
2. 循环
首先我们来看看对任何集合类都可用的foreach方法(新特性),以下是实例代码:
Test01ForEach.java
public class Test01ForEach {
public static void main(String[] args) {
List<Person> pl = Person.createShortList();
System.out.println("\n=== Western Phone List ===");
pl.forEach( p -> p.printWesternName() );
System.out.println("\n=== Eastern Phone List ===");
pl.forEach(Person::printEasternName);
System.out.println("\n=== Custom Phone List ===");
pl.forEach(p -> { System.out.println(p.printCustom(r -> "Name: " + r.getGivenName() + " EMail: " + r.getEmail())); });
}
}
第一个foreach使用标准的lambda表达式调用printWesternName方法打印列表内的人员对象。第二个foreach表达式展示了方法调用(method
reference)。在这个例子里,Person类中存在一个能够执行操作的方法(printEasternName),这种语法可以用来替代正常的lambda语法。最后一个foreach展示了如何在这种情景下中使用printCustomer方法。注意到在参数名方面的细微差异,Lambda表达式包含在另一个lambda表达式内。
你可以通过迭代(iterate)的方式遍历集合。Foreach只是for循环的增强版。然而,将迭代机制包含在类中能够提供一系列的好处。
3. 串联与过滤(Filters)
首先,你可以将方法都串联在一起。让我们来看看第一个方法filter,filter能够将Predicate接口对象作为参数。下面的例子遍历过滤之后的集合元素。
Test02Filter.java
public class Test02Filter {
public static void main(String[] args) {
List<Person> pl = Person.createShortList();
SearchCriteria search = SearchCriteria.getInstance();
System.out.println("\n=== Western Pilot Phone List ===");
pl.stream().filter(search.getCriteria("allPilots"))
.forEach(Person::printWesternName);
System.out.println("\n=== Eastern Draftee Phone List ===");
pl.stream().filter(search.getCriteria("allDraftees"))
.forEach(Person::printEasternName);
}
}
两个循环都展示了如何根据查询条件来过滤集合,第二个循环的输出结果如下:
=== Eastern Draftee Phone List ===
Name: Baker Bob
Age: 21 Gender: MALE
EMail: [email protected]
Phone: 201-121-4678
Address: 44 4th St, Smallville, KS 12333
Name: Doe John
Age: 25 Gender: MALE
EMail: [email protected]
Phone: 202-123-4678
Address: 33 3rd St, Smallville, KS 12333
使之懒惰
上述的特性都很有用,但现在已经有了几乎完美的for循环,还需要增加它们到集合类中完成遍历?增加迭代的特性到类库,将允许Java开发者做更多的代码优化动作。为了更好地解释,有两个概念需要定义:
l 懒惰(Laziness):在编程里,懒惰指的是仅仅处理你想让程序处理的对象。在前面的案例中,循环是“懒惰”的,在过滤之后,它仅仅遍历两个Person对象。代码也变得更加有效,因为只需要处理符合条件的对象。
l 积极(Eagerness):对列表里每一个对象进行操作被认为是“积极”的。比如,为了处理两个Person对象而对整个列表进行遍历,被认为是更加“积极”的途径。
通过将只遍历部分对象的循环加入类库中,“懒惰”优化就更加容易实现。“积极”操作在计算求和、平均数等依然适用,在这样的情形中适用“积极”操作是更加有效和灵活地方法。
Stream 方法
在前面的代码中,注意在调用过滤和遍历之前,使用了stream方法。该方法将集合作为输入并返回java.util.stream.Stream接口作为输出。Stream表示一系列串联起来的方法。如果一个元素使用完了,stream就会删除它。因此,使用stream就能让一系列的操作仅仅出现一次。另外Stream能够序列化或并行,这取决于被调用的方法。并行stream出现在该节末。
4. 修改与结果
前面提到,Stream返回的是Stream对象,因此,集合中的对象不能通过Stream来修改。然而,如果你想保存链接操作之后的集合元素怎么办?你可以把它们都存储到一个新的集合中。下面的代码将教你怎么做。
Test03toList.java
public class Test03toList {
public static void main(String[] args) {
List<Person> pl = Person.createShortList();
SearchCriteria search = SearchCriteria.getInstance();
// Make a new list after filtering.
List<Person> pilotList = pl
.stream()
.filter(search.getCriteria("allPilots"))
.collect(Collectors.toList());
System.out.println("\n=== Western Pilot Phone List ===");
pilotList.forEach(Person::printWesternName);
}
}
Collect方法只有一个参数,Collectors类。Collectors类根据stream的结果返回List或Set。该例展示了如何将迭代之后的结果赋值给新的List。
5. 使用map做计算
Map方法通常和filter一起使用。该方法使用类中的属性做一些操作。下面例子将展示年龄的一些操作。
Test04Map.java
import java.util.List;
import java.util.OptionalDouble;
/**
*
* @author MikeW
*/
public class Test04Map {
public static void main(String[] args) {
List<Person> pl = Person.createShortList();
SearchCriteria search = SearchCriteria.getInstance();
// Calc average age of pilots old style
System.out.println("== Calc Old Style ==");
int sum = 0;
int count = 0;
for (Person p:pl){
if (p.getAge() >= 23 && p.getAge() <= 65 ){
sum = sum + p.getAge();
count++;
}
}
long average = sum / count;
System.out.println("Total Ages: " + sum);
System.out.println("Average Age: " + average);
// Get sum of ages
System.out.println("\n== Calc New Style ==");
long totalAge = pl
.stream()
.filter(search.getCriteria("allPilots"))
.mapToInt(p -> p.getAge())
.sum();
// Get average of ages
OptionalDouble averageAge = pl
.parallelStream()
.filter(search.getCriteria("allPilots"))
.mapToDouble(p -> p.getAge())
.average();
System.out.println("Total Ages: " + totalAge);
System.out.println("Average Age: " + averageAge.getAsDouble());
}
}
该类的输出如下:
== Calc Old Style ==
Total Ages: 150
Average Age: 37
== Calc New Style ==
Total Ages: 150
Average Age: 37.5
程序计算列表中飞行员的平均年龄,第一个循环展示了使用for循环的方式。第二个循环使用map方法获取在序列stream中的每一个飞行员的年龄,totalAge是一个long类型数据。Map方法返回一个IntStream对象,它包含一个Sum方法返回一个long对象。
注意:在第二次计算平均值的时候,求和计算就没有必要了。这里只是展示sum方法的用法。
第三个循环计算stream中的平均年龄,注意为了让结果能够并行计算,这里使用了并行Stream方法来获取一个并行的stream。另外,返回值有一点点不同。
总结
在这篇教程里面,你学习了如何使用:
l Java匿名内部类
l 在Java SE 8 中用Lambda表达式取代匿名内部类
l 正确的lambda表达式用法
l 使用Predicate接口搜索列表
l 使用Function接口处理不同类型的对象
l Java SE 8 中Collections的新特性。
致谢:
l Lead Curriculum 开发者:Michael Willams
l QA:Juan Quesada Nunez
资源: