vlambda博客
学习文章列表

抽丝剥茧java8函数式编程,生产环境下应该这么用



每天分享技术干货

视频 | 电子书 | 面试题 | 开发经验

我就知道你“ 在看




摘要:在Java重构的过程中,巧妙的运用函数式思想能够便捷地去掉重复。

函数式编程是声明式的。也就是说,她应该指定“什么要做”而非“怎么做”。这种方式使得我们可以工作更高的抽象层次。而传统的过程式以及面向对象的语言,则是命令式的,因而更关注于“怎么做”这个层面。站在面向对象思想的角度来看,函数式编程将函数看成一等公民的思想,使得我们处理的粒度从类变小为函数,从而可以更好地满足系统对重用性和扩展性的支持。也就是说,我们可以从函数的粒度,而非对象的粒度去思考领域问题。

例如,有这样一个场景:我的业务模型是个Person,已经有业务接口能返回给我所有Person的list集合,我需要把根据一些条件返回符合对应关系的Person。

Person.java

public class Person {
    private String name;
    private String gender;
    private int age;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

首先,根据业务需求,写出第一个方法 通过姓名找出符合的Person

PersonService.java

import java.util.ArrayList;
import java.util.List;

public class PersonService {
 //假设这个list是通过已有接口返回的所有Person集合
    private static List<Person> list = new ArrayList<Person>();

    public static List<Person> findByName(String name){
        List<Person> people = new ArrayList<Person>();
        for (Person p : list){
            if(name.equals(p.getName())){
                people.add(p);
            }
        }
        return people;
    }
}

接下来,第二个方法根据性别找出符合的Person

import java.util.ArrayList;
import java.util.List;

public class PersonService {
    //假设这个list是通过已有接口返回的所有Person集合
    private static List<Person> list = new ArrayList<Person>();

    public static List<Person> findByName(String name){
        List<Person> people = new ArrayList<Person>();
        for (Person p : list){
            if(name.equals(p.getName())){
                people.add(p);
            }
        }
        return people;
    }

    public static List<Person> findByGender(String gender){
        List<Person> people = new ArrayList<Person>();
        for (Person p : list){
            if(gender.equals(p.getGender())){
                people.add(p);
            }
        }
        return people;
    }
}

功能是实现了,但是要是这样交差,估计得挨骂了,这么多重复代码。仔细观察刚添加的两个方法。发现,除了下面两行以外全都相同:

  • if(name.equals(p.getName())){
  • if(gender.equals(p.getGender())){

这里的namegender是两个字符串参数,可以给它们用同样的名字,比如str,这样的话:

  • if(str.equals(p.getName())){
  • if(str.equals(p.getGender())){

这下,不同点就是getName()getGender(),是两个方法,而Java是不能把函数当参数的,这样重构起来就麻烦很多。

想到Java中还有一种迂回的方式传递函数,那就是接口。

  • 新建一个接口 Criteria,在这个接口里隐藏if条件语句里面的实现细节。这样就可以把 findByName()findByGender 高度抽象为一个方法find()。
import java.util.ArrayList;
import java.util.List;

public class PersonService {
    //假设这个list是通过已有接口返回的所有Person集合
    private static List<Person> list = new ArrayList<Person>();

    public List<Person> findByName(String name) {
  return null;
    }

    public List<Person> findByGender(String gender) {
        return null;
    }

    public List<Person> find(Criteria criteria){
        List<Person> people = new ArrayList<Person>();
        for (Person p : list){
            if(criteria.matches(p)){
                people.add(p);
            }
        }
        return people;
    }
}

interface Criteria{
    boolean matches(Person person);

}

对于具体的findByName()方法,只需要实现匹配的细节即可:

public List<Person> findByName(String name) {
        return find(new Criteria() {
            @Override
            public boolean matches(Person person) {
                return person.getName().equals(name);
            }
        });
    }

经过改装后的PersonService.java大致像下面这样:

import java.util.ArrayList;
import java.util.List;

public class PersonService {
    //假设这个list是通过已有接口返回的所有Person集合
    private List<Person> list = new ArrayList<>();

    public List<Person> findByName(String name) {
        return find(new Criteria() {
            @Override
            public boolean matches(Person person) {
                return name.equals(person.getName());
            }
        });
    }

    public List<Person> findByGender(String gender) {
       return find(new Criteria() {
           @Override
           public boolean matches(Person person) {
               return gender.equals(person.getGender());
           }
       });
    }

    public List<Person> find(Criteria criteria){
        List<Person> people = new ArrayList<>();
        for (Person p : list){
            if(criteria.matches(p)){
                people.add(p);
            }
        }
        return people;
    }

}

interface Criteria{
    boolean matches(Person person);
}

看到这,估计有人要发飙了,说好的重构去重复,结果为了一行代码,增加了无数行无关代码,还无故多了一个类(接口Criteria)。原始代码才25行左右,现在将近40行代码。这个findByName()findByGender方法除了return后面的不同,剩下的完全一样,虽然那些代码都是IDE自动生成的,但是看起来,这次重构没有起到简化代码的目的。甚至,如果你没有用Java8,那上面的代码需要改动才能编译通过。

这种重构的思想其实属于一种设计模式——策略设计模式,它的意图就是定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。

当然,如果使用过java8,那么应该也听说过Lambda表达式。如果使用的IDE是Intellij的话,那应该庆幸一下,因为它已经识别到了需要改进的地方。

这样,看起来,代码会整洁许多:

import java.util.ArrayList;
import java.util.List;

public class PersonService {
    //假设这个list是通过已有接口返回的所有Person集合
    private List<Person> list = new ArrayList<>();

    public List<Person> findByName(String name) {
        return find(p-> name.equals(p.getName()));
    }

    public List<Person> findByGender(String gender) {
       return find(p-> gender.equals(p.getGender()));
    }

    public List<Person> find(Criteria criteria){
        List<Person> people = new ArrayList<>();
        for (Person p : list){
            if(criteria.matches(p)){
                people.add(p);
            }
        }
        return people;
    }

}

interface Criteria{
    boolean matches(Person person);
}

我定义了一个看起来多余,但是又必须存在的接口Criteria。如果项目中有很多需要这样重构的代码,不是得写很多类似的接口?

当然,JDK的设计者已经想到了这一点,于是就有了一个公用的接口Predicate。这样,再整理一下代码:

import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;

public class PersonService {
    //假设这个list是通过已有接口返回的所有Person集合
    private List<Person> list = new ArrayList<>();

    public List<Person> findByName(String name) {
        return find(p-> name.equals(p.getName()));
    }

    public List<Person> findByGender(String gender) {
       return find(p-> gender.equals(p.getGender()));
    }

    public List<Person> find(Predicate<Person> predicate){
        List<Person> people = new ArrayList<>();
        for (Person p : list){
            if(predicate.test(p)){
                people.add(p);
            }
        }
        return people;
    }
}

如果觉得这里的find写的臃肿,也可以改进:

整理后的代码:

import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

public class PersonService {
    //假设这个list是通过已有接口返回的所有Person集合
    private List<Person> list = new ArrayList<>();

    public List<Person> findByName(String name) {
        return find(p -> name.equals(p.getName()));
    }

    public List<Person> findByGender(String gender) {
       return find(p -> gender.equals(p.getGender()));
    }

    public List<Person> find(Predicate<Person> predicate){
        return list.stream().filter(p -> predicate.test(p)).collect(Collectors.toList());
    }
}

这样看起来就比刚开始复制/粘贴的代码清爽许多。如果第三个业务需求是根据年龄找出符合条件的Person,你可以这样写:

public List<Person> findByAge(int age){
        List<Person> people = new ArrayList<>();
        for (Person person : list) {
            if(age == person.getAge()){
                people.add(person);
            }
        }
     return people;
}

中规中矩,没有问题,但是如果写成下面这样,是不是更容易抓住方法的要点:

public List<Person> findByAge(int age){
     return find(p -> age == p.getAge());
}

刚开始重构花了很多时间,但是现在要新添加业务方法,我只需要一行代码:

import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

public class PersonService {
    //假设这个list是通过已有接口返回的所有Person集合
    private List<Person> list = new ArrayList<>();

    public List<Person> findByName(String name) {
        return find(p -> name.equals(p.getName()));
    }

    public List<Person> findByGender(String gender) {
       return find(p -> gender.equals(p.getGender()));
    }
 //根据年龄找出符合条件的Person
    public List<Person> findByAge(int age){
        return find(p -> age == p.getAge());
    }

    public List<Person> find(Predicate<Person> predicate){
        return list.stream().filter(p -> predicate.test(p)).collect(Collectors.toList());
    }
}

之后又有一个新的业务需求找出在给定年龄段的Person,依然只需要一行代码:

import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

public class PersonService {
    //假设这个list是通过已有接口返回的所有Person集合
    private List<Person> list = new ArrayList<>();

    public List<Person> findByName(String name) {
        return find(p -> name.equals(p.getName()));
    }

    public List<Person> findByGender(String gender) {
       return find(p -> gender.equals(p.getGender()));
    }

    public List<Person> findByAge(int age){
        return find(p -> age == p.getAge());
    }
 //找出在给定年龄段的Person
    public List<Person> findByAgeRange(int startAge,int endAge){
        return find(p -> startAge <= p.getAge() && endAge >= p.getAge());
    }

    public List<Person> find(Predicate<Person> predicate){
        return list.stream().filter(p -> predicate.test(p)).collect(Collectors.toList());
    }
}

Java8中引入的函数式接口Lambda表达式,以及Stream API给Java增加了更多的可能,在面向对象的语言中增加面向函数的设计思想,使得某些场景下代码的编写异常简洁清晰。



扫描关注

点击留言








独学而无友,

则孤陋而寡闻