您的位置:首页 > 编程语言 > Java开发

JavaSE 8 :Lambda 快速学习(二)

2014-02-25 22:18 225 查看
使用Lambda表达式改进代码
本章节根据前面的例子来向你展示Lambda表达式是如何改进你的代码的.Lambda表达式更好地支持不要重复自己(Donot
Repeat Yourself)原则,并使你的代码更简单和更具有可读性。
一个常见的查询用例
一个常见的编程用例就是根据特定的规则从数据集合中查找元素。在2012年JavaOne大会上精彩的“Jump-Starting
Lambda”演说中,Stuart Marks 和 MikeDuigou就是使用这个用例做示范。给一个人名单,使用不同规则让机器人和匹配的人通话。这个教程也遵循这一基本前提但稍微有些变化。
在这个示例中,我们的信息需要区分在美国的三个不同的群体。

司机:年龄在16岁以上的

应征者:年龄在18-25岁的男性
飞行员(特指商业飞行员):年龄在23-65之间

能够完成这些任务的真实机器人还没有进入商用阶段.这里不打电话,邮寄或者发送电子邮件,取而代之是将信息在控制台打印出来.信息包含一个人的名字,年龄和一些特定的媒体信息(例如用来发送电邮的电子邮箱地址,打电话所需要的电话号码)

Person Class
在测试名单中的人都是使用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;
这个Person类使用Builder来创建新对象.示例的人名单列表是使用createShortList
方法创建.下面是该方法的阶段代码片段.注意:这个教程所有的源代码都包含在一个NetBeans工程中,在这一章节的最后有链接地址。

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()
);


第一次尝试
Pserson类和查询条件都已经定义好,现在你可以写一个RoboContant类。一个可行的解决方法是给每一个用例定义一个方法:
RoboContactsMethods.java
package com.example.lambda;
import java.util.List;

/**
*
* @author MikeW
*/
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)中明白这些方法描述的都是一种正在发生的行为。只要传达明确的查询条件,机器人根据适当的调用做出动作。可是,这样的设计也有几个不好的地方:

没有遵循“不要重复自己”(DRY)原则

每个方法都重复循环机制

每个方法的选择条件必须重写

针对每个用例好多方法需要实现
代码不灵活。一旦查询条件变化,许多的代码都需要为一个更新而改变。此外,这个代码也不好维护。

重构方法
怎么修改代码呢?从查询条件开始比较好。如果测试条件能够独立放在一个方法里,将会好很多。

RoboContactMethods2.java

package com.example.lambda;
import java.util.List;

/**
*
* @author MikeW
*/
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表达式之前,匿名内部类是一种选择。例如给一个接口(MyTest.java)定义一个Test方法返回布尔值(函数式接口)是一个可行的解决方法。查询条件当这个方法被调用的时候被传递过去。这个接口就像下面的那样:
public interface MyTest<T> {
public boolean test(T t);
}


更新之后的robot类就像这样的:
RoboContactsAnon.java
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
package com.example.lambda;
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函数式接口将匿名内部类传给方法.可是,不必要再单独写个接口.Java
SE 8提供了java.util.function包里面有许多标准的函数式接口.这个用例中,Predicate接口符合我们的需求。
public interface Predicate<T> {
public boolean test(T t);
}


test方法带有一个泛型类和返回一个boolean值。这正好是条件选择所需要的。下面是robot类的最后版本。

RoboContactsLambda.java
package com.example.lambda;

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实体。
“垂直问题”解决
Lambda表达式解决了垂直问题,任何表达式都能够很容易的复用。再来看一下经过Lambda表达式更改之后新的测试类。
RoboCallTest04.java
package com.example.lambda;
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 接口给这些联系方法。代码紧凑很容易读而且没有重复。

本章源码
点击打开链接
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: