JavaSE 8 :Lambda 快速学习(二)

使用Lambda表达式改进代码

本章节根据前面的例子来向你展示Lambda表达式是如何改进你的代码的.Lambda表达式更好地支持不要重复自己(Donot Repeat Yourself)原则,并使你的代码更简单和更具有可读性。

一个常见的查询用例

一个常见的编程用例就是根据特定的规则从数据集合中查找元素。在2012JavaOne大会上精彩的“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 接口给这些联系方法。代码紧凑很容易读而且没有重复。


本章源码

           点击打开链接

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值