Java SE 8: Lambda Quick Start Part.II

使用lambda表达式改进代码

本节基于前面的示例来展示lambda表达式如何改进代码. lambda应该提供一种方法来更好地支持不重复自己(DRY)原则, 使你的代码更简单,更可读。

常见查询用例

一个常见的编程用例是搜索数据集合以查找符合特定条件的项目. 在JavaOne 2012的出色的"Jump-Starting Lambda"演示中, Stuart Marks和Mike Duigou演示过这样的用例. 给定人的列表, 使用各种标准来向匹配的人做出robo呼叫(自动电话呼叫). 本教程遵循稍微变化的基本前提.
在这个例子中, 我们的消息需要到美国的三个不同的组: * 司机: 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方法来创建新对象. 一个示例people列表由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("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()
  );
}

首次尝试

在定义了Person类和搜索条件后, 您可以编写RoboContact类. 一种可能的解决方案是为每个用例定义一个方法:

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原则
    • 每种方法重复循环机制
    • 必须为每个方法重写选择条件
  • 需要大量的方法来实现每个用例
  • 代码不灵活. 如果搜索条件更改, 则需要对更新进行大量代码更改. 因此, 代码可维护性不好

重构方法

如何修正这个类? 搜索条件是一个很好的起点. 如果测试条件被分离到单独的方法中, 那将是一种改进.

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表达式之前, 匿名内部类曾是一种选择. 比如, 一个函数式接口就是一个可行的解决方案. 当这个接口被调用时, 搜索条件就可以传入. 接口看起来像这样:

public interface MyTest<T>{
  public boolean test(T t);
}

而更新了的自动化类如下:

package com.example.lambda;

import java.util.List;

/**
 *
 * @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());
  }

}

这无疑是另一个进步, 因为只需要三个方法就能实现自动化操作. 然而, 当调用方法时还有一个丑陋的小问题. 查看用于此类的测试类:

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方法接收一个泛型类并返回布尔值. 这正适合用来做选择. 最终的代码如下:

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表达式更新的新测试类.

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接口中的任何一个传递给联系方法. 代码紧凑, 易于阅读, 不拖沓.

源代码

转载于:https://my.oschina.net/CasparLi/blog/799527

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值