JAVA Lambda与Predicate应用

举报
滕磊 发表于 2021/12/31 17:50:13 2021/12/31
1.9k+ 0 0
【摘要】 Java Lambda介绍 什么是Lambda? 为什么要使用Lambda? Predicate类基本使用 Predicate介绍 实战:使用lambda+predicate优化spark内核代码 Java Lambda介绍 什么是Lambda?先来看简单的一段覆写方法的代码:Comparator<Integer> compare = new Comparator<Integer>() {...

Java Lambda介绍

什么是Lambda?

先来看简单的一段覆写方法的代码:

Comparator<Integer> compare = new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return Integer.compare(o1, o2);
    }
};
TreeSet<Integer> treeSet = new TreeSet<>(compare);

仔细观察可以知道,原来java里头的覆写方法块中,真正有用的也就是一个

return Integer.compare(o1, o2);

其它的代码本质上都是”冗余“的。

此时如果使用Lambda表达式对这块代码进行重写的话,只需要两行:

Comparator<Integer> com = (x, y) -> Integer.compare(x, y);

//上面一行还可以写成Comparator<Integer> com = Integer::compare;

TreeSet<Integer> treeSet = new TreeSet<>(com);

很显然,Lambda表达式最明显的特点就是一行代码搞定多行代码的功能,但这显然不能成为咱们使用Lambda表达式的理由

为什么要使用Lambda?

  • 上面只是一个简单的例子,这会咱们来个稍微复杂一点点的例子,对比一下原生java使用内部表达式和Lambda表达式哪种方式更好

    • 例如:这会有个需求,获取当前业务中充值超过30000的客户信息

      //首先创建Customer类定义了客户有啥信息
      @Data
      @Builder
      @ToString
      @NoArgsConstructor
      @AllArgsConstructor
      public class Customer implements Serializable {
          private static final long serialVersionUID = -9329712407749463152L;
          private String name;
          private int priority;
          private boolean vip;
          private int money;
      }
      
      //在Customer里头,咱们存储了客户的名字,优先级,是否是vip和充值的钱,并且创建了存储多个客户的List集合
      protected List<Customer> customers = Arrays.asList(
      		new Customer("百度", 5, false, 11111),
      		new Customer("阿里", 4, false, 22222),
      		new Customer("腾讯", 3, true, 33333),
      		new Customer("字节", 2, true, 44444),
      		new Customer("大老板", 1, true, 999999999)
      );
      

      常规方法

      public List<Customer> filterCustomersByMoney(List<Customer> customers) {
          List<Customer> result = new ArrayList<>();
          for (Customer customer : customers) {
              if (customer.getMoney() >= 30000) {
                  result.add(customer);
              }
          }
          return result;
      }
      
      @Test
      public void test() {
          List<Customer> customers = filterCustomersByMoney(this.customers);
          for (Customer customer : customers) {
              System.out.println(customer);
          }
      }
      

      看上去有点麻烦对吧,要是这会需求还发生了变化,获取当前客户里头优先级大于或等于3的客户信息,这会咱们又不得不整一个按优先级过滤的方法:

      public List<Customer> filterCustomersByPriority(List<Customer> customers) {
          List<Customer> result = new ArrayList<>();
          for(Customer customer : customers) {
              if(customer.getPriority() >= 3) {
                  result.add(customer);
              }
          }
          return result;
      }
      
      @Test
      public void test() {
          List<Customer> customers = filterCustomersByPriority(this.customers);
          for (Customer customer : customers) {
              System.out.println(customer);
          }
      }
      

      这时,咱们仔细对比filterCustomersByPriority和filterCustomersByMoney,可以发现它俩也就循环中对条件的判断不同而已:

      public List<Customer> filterCustomersByMoney(List<Customer> customers) {
          List<Customer> result = new ArrayList<>();
          for (Customer customer : customers) {
              if (customer.getMoney() >= 30000) {
                  result.add(customer);
              }
          }
          return result;
      }
      
      public List<Customer> filterCustomersByPriority(List<Customer> customers) {
          List<Customer> result = new ArrayList<>();
          for(Customer customer : customers) {
              if(customer.getPriority() >= 3) {
                  result.add(customer);
              }
          }
          return result;
      }
      

      要是这会再来一个需求,获取所有vip客户的信息,那咱们又得加一个函数,而且结构和上面差不多…

    • 使用策略模式优化代码(以获取所有vip客户的信息为例):

      先定义一个泛型接口MyPredicate, 对传递过来的数据进行过滤,符合规则返回true,否则返回false

      public interface MyPredicate<T> {
      
          /**
           * 对传递过来的T类型的数据进行过滤
           * 符合规则返回true,不符合规则返回false
           */
          boolean filter(T t);
      }
      

      然后定义一个MyPredicate接口的实现FilterCustomersByVip

      public class FilterCustomersByVip implements MyPredicate<Customer> {    @Override    public boolean filter(Customer customer) {        return customer.isVip();    }}
      

      接着定义一个过滤客户信息的方法,传递的信息包括了客户集合与咱们定义的接口实例,再遍历的时候将符合过滤条件的客户信息返回

      //优化方式一public List<Customer> filterCustomer(       	List<Customer> customerList,	    MyPredicate<Customer> myPredicate) {    List<Customer> result = new ArrayList<>();    for (Customer customer : customerList) {        if (myPredicate.filter(customer)) {            result.add(customer);        }    }    return result;}@Testpublic void test() {    List<Customer> customerList =        this.filterCustomer(this.customers, new FilterCustomersByVip());    for (Customer customer : customerList) {        System.out.println(customer);    }}
      

      这时可以看出,咱们把变化的部分抽象出来成为了一个接口,若此时还需要获取充值超过30000的客户信息,只需要新建一个FilterCustomerByMoney类实现MyPredicate接口即可

      public class FilterCustomerByMoney implements MyPredicate<Customer> {    @Override    public boolean filter(Customer customer) {        return customer.getMoney() >= 30000;    }}@Testpublic void test() {    List<Customer> customerList = this.filterCustomer(this.customer, new FilterCustomerByMoney());    for (Customer customer : customerList) {        System.out.println(customer);    }}
      

      这时候咱们已经可以把不变-变化的地方解耦,让代码逻辑更清晰,但是,能不能继续优化呢?答案当然是:能!

    • 大家很容易就发现了,即使咱们使用了设计模式解耦了代码结构,很明显这代码还存在一个很不好的地方:每次定义一个过滤策略的时候,咱们都要单独创建一个过滤类!

    • 题外话:需要注意的是,这种每次都单独创建一个过滤类用于策略过滤的方法是有其适用场景的,并不是说一定不好,得结合具体使用场景,比如需要通过配置文件去指定使用哪个策略类的话,咱们就可以把策略类用反射的方式调用,而类名则放在配置文件里头,达到不需要修改代码就可以灵活改变过滤策略的目的

  • 使用匿名内部类来优化书写

    • 尝试使用匿名内部类来实现对客户信息的过滤

      先来看看过滤充值数额超过30000的客户信息

      @Testpublic void test() {    List<Customer> customerList =        this.filterCustomer(this.customers, new MyPredicate<Customer>() {        @Override        public boolean filter(Customer customer) {            return customer.getMoney() > 30000;        }    });    for (Customer customer : customerList) {        System.out.println(customer);    }}
      

      再看看过滤优先级大于3的客户信息(优先级越高,数字越小):

      @Testpublic void test() {    List<Customer> customerList =        this.filterCustomer(this.customers, new MyPredicate<Customer>() {        @Override        public boolean filter(Customer customer) {            return customer.getPriority >= 3;        }    });    for (Customer customer : customerList) {        System.out.println(customer);    }}
      

      可以看出,匿名内部类看起来比常规遍历集合简单一些,并且不需要每次创建一个类来实现过滤规则,使得代码进一步简化了

      但是!匿名类的写法有一个致命缺点,就是可读性不高,而且冗余代码比较多!

      那能不能继续优化呢?答案当然是:能!

    Lambda表达式

    注意:使用Lambda表达式前,咱们还是要调用

    filterCustomer(List<Customer> list, MyPredicate<Customer> myPredicate)方法

    public List<Customer> filterCustomer(       	List<Customer> customerList,	    MyPredicate<Customer> myPredicate) {    List<Customer> result = new ArrayList<>();    for (Customer customer : customerList) {        if (myPredicate.filter(customer)) {            result.add(customer);        }    }    return result;}
    

    来看看这时过滤充值数额超过30000的客户信息

    @Testpublic void test() {    filterCustomer(customers, (customer) -> customer.getMoney()                    > 30000).forEach(System.out::println);}
    

    过滤优先级大于3的客户信息

    @Testpublic void test(){    filterCustomer(this.customers, (customer) -> customer.getPriority()                    >= 3).forEach(System.out::println);}
    

    是不是很简单,一行Lambda就可以完成了客户信息的输出,而且核心过滤逻辑明了,几乎没有冗余代码,一眼就了解了这块代码是做啥的.

    那么到这里就是终点了吗?当然不是

Predicate类基本使用

Predicate介绍

Predicate<T>是java自带的一个接口,直接上源码

public interface Predicate<T> {    boolean test(T t);    default Predicate<T> and(Predicate<? super T> other) {        Objects.requireNonNull(other);        return (t) -> test(t) && other.test(t);    }    default Predicate<T> negate() {        return (t) -> !test(t);    }    default Predicate<T> or(Predicate<? super T> other) {        Objects.requireNonNull(other);        return (t) -> test(t) || other.test(t);    }    static <T> Predicate<T> isEqual(Object targetRef) {        return (null == targetRef)                ? Objects::isNull                : object -> targetRef.equals(object);    }}

仔细观察,Java里面的Predicate接口咱们只需要实现boolean test(T t)就行了

是不是有点眼熟,好像在哪见到过?没错这东东和我们刚刚自己定义的MyPredicate长得贼像:

public interface MyPredicate<T> {    /**     * 对传递过来的T类型的数据进行过滤     * 符合规则返回true,不符合规则返回false     */    boolean filter(T t);}

这不就对应咱们MyPredicate<T>里头的filter了吗?

回到咱们之前的例子,咱们最后一步优化到这里

@Testpublic void test(){    filterCustomer(this.customers, (customer) -> customer.getPriority()                    >= 3).forEach(System.out::println);}public List<Customer> filterCustomer(       	List<Customer> customerList,	    MyPredicate<Customer> myPredicate) {    List<Customer> result = new ArrayList<>();    for (Customer customer : customerList) {        if (myPredicate.filter(customer)) {            result.add(customer);        }    }    return result;}

这时候咱们还能更进一步,使用java lambda表达式与java 原生Predicate接口优化

过滤充值数额超过30000的客户信息

@Testpublic void test(){    this.customers.stream().filter(customer->customer.getMoney() > 30000).foreach(System.out::println);}

实战:使用lambda+predicate优化spark内核代码

spark内核中存在一个性能提升场景:

当前有一个待处理的数据集taskData

1、需要过滤出stageData中status为IN_ACTIVE的stage集合,称为stages

2、遍历stages并根据stages里头的stageId去taskData集合中找到stageId相等的task,并删除对应的task

List<Task> taskDataList<Stage> stageData    Task {	String stageId;    String taskData;}Stage {    String stageId;    String status;}

1、初步方案:遍历stageData的同时,根据获取到的stageId再去遍历taskData,此时时间复杂度为O(n*m)

public static void removeTaskData() {	for(Stage stage:stageData) {		if(stage.status == IN_ACTIVE) {			Iterator iterator = taskData.iterator()			while(iterator.hasNext()) {				if(iterator.next().stageId == stage.stageId) {					iterator.remove();				}			}		}	}}

2、此时可以看出,嵌套层数较深,当taskData和stageData数据量大的时候会十分耗时

改进方案:先将所需要删除的stageId统计出来,再利用Set中的contains方法判断删除,此时时间复杂度为O(n) + O(m)

public static void removeTaskData() {	Set<Stage> stageIds = new HashSet<>;	for(Stage stage:stageData){		if(stage.status == IN_ACTIVE){			stageId.add(stage);		}	}    	Iterator iterator = taskData.iterator();	while(iterator.hasNext()) {		if(stageIds.contains(iterator.next().stageId)) {			iterator.remove();		}	}}

3、重构方案:使用javav自带的Predicate与Lambda优化代码结构,直接使用stream操作,使得遍历taskData时更优雅

public static void removeTaskData() {	Set<Stage> stageIds = stageData.stream()                 		 		   .filter(s->s.status == IN_ACTIVE)                                   .collect(Collectors.toSet());        List<TaskData> taskToDelete = taskData.stream()                                          .filter(taskData->stageIds.contaions(taskData.stageId))                                          .collect(Collectors.toList()); 	taskToDelete.forEach(taskData::remove);}
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

热门文章

评论(0

抱歉,系统识别当前为高风险访问,暂不支持该操作

    全部回复

    上滑加载中

    设置昵称

    在此一键设置昵称,即可参与社区互动!

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。