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

读书笔记——《Java 8实战》系列之行为参数化

2017-10-31 11:16 597 查看
转自 http://www.wxueyuan.com/blog/articles/2017/10/13/1507857576802.html

最近在读《Java 8实战》这本书,记录下来一些书中的重点知识和大家分享,文中使用了一些书中的例子,特此声明。

如果有朋友对这本书感兴趣但是又没有时间看的话,可以看我的博客来大概了解此书的重点知识。

行为参数化顾名思义就是将一段代码块(一个行为)当作参数传递给另一个方法,从而使你程序的其它部分能够使用。它最大的好处就是可以将代码推迟执行,以便于处理频繁更换的请求。

举个生活中常见的例子吧,假设我们现在有一个学生类Student

class Student{
private String name;           //学生姓名
private int avgScore;          //平均成绩
private int height;            //学生身高

public Student(String name, int avgScore, int height) {
super();
this.name = name;
this.avgScore = avgScore;
this.height = height;
}

public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAvgScore() {
return avgScore;
}
public void setAvgScore(int avgScore) {
this.avgScore = avgScore;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}

}


如果我们现在需要提供一个方法来筛选出所有身高在180cm以上同学,我们会怎么做呢?

//参数students是所有学生的集合,返回一个符合条件的的
public static List<Student> getHighStudent(List<Student> students){

}


这个方法当然难不住各位了,三两下就可以完成

public static List<Student> getHighStudent(List<Student> students){
List<Student> highStudents = new ArrayList<>();
for(Student s : students) {
if(s.getHeight()>=180)
highStudents.add(s);
}
return highStudents;
}


如果此时突然需求发生了变化,要求方法筛选出所有身高在160cm以上同学,我们又要怎么做呢?

复制一遍上面的代码,将180改为160?这种方法明显不可取,如果需求又发生了变化,我们不可能再去改一遍数字吧。

这种写代码的方式不符合我们抽象化的原则。

当然大部分同学会想到解决方案,将身高当作参数放入方法当中

public static List<Student> getStudentByHeight(List<Student> students, int height){
List<Student> highStudents = new ArrayList<>();
for(Student s : students) {
if(s.getHeight()>=height)
highStudents.add(s);
}
return highStudents;
}


此时,我们就不太担心需求的变化了,无论要求我们获得身高多少以上的学生,我们都可以将它当作参数传入到方法去。

同样的道理,如果我们需要获取平均成绩在90分或80分以上的学生,只需要复制之前的代码,将height参数改为avgScore即可。

public static List<Student> getStudentByAvgScore(List<Student> students, int avgScore){
List<Student> highStudents = new ArrayList<>();
for(Student s : students) {
if(s.getHeight()>=avgScore)
highStudents.add(s);
}
return highStudents;
}


这样做看似很简单,但其实它大部分都是在进行代码的复制getStudentByHeight()和getStudentByAvgScore()两个方法的区别竟然只有传入的参数不同而已。

如果再有一个需求删选出所有年纪大于18岁的学生呢?我们难道要一直复制并修改上面的代码么

那么有没有更加优质,抽象的方法来解决这个问题呢?

答案就是今天的标题行为参数化

首先我们先抽象一下我们目前面对的问题,我们考虑的对象是学生,而我们的需求是获取所有满足某些条件(这里的条件是学生的某些属性)的学生。

我们可以抽象出来一个方法,如果满足了所有的条件则返回true,否则返回false

像这种根据某些条件是否满足来返回一个Boolean值的函数,我们称之为谓词(predicate)

那么我们现在来抽象出一个接口来进行建模。

interface StudentPredicate{
//由于我们需要满足的条件是学生的属性,因此传入的参数直接写为Student实例即可
boolean test(Student s);
}


针对我们需要处理的问题获取满足身高大于180cm的学生和获取满足平均成绩大于90的学生,我们可以建立两个类并实现上面的接口

class StudentHeightPredicate implements StudentPredicate{

@Override
public boolean test(Student s) {
if(s.getHeight()>=180)
return true;
return false;
}

}

class StudentAvgScorePredicate implements StudentPredicate{

@Override
public boolean test(Student s) {
if(s.getAvgScore()>=90)
return true;
return false;
}

}


那么当我们在需要筛选出符合某项条件的所有学生时,我们就可以这样写了:

public static List<Student> studentFilter(List<Student> students, StudentPredicate predicate){
List<Student> highStudents = new ArrayList<>();
for(Student s : students) {
//如果谓词方法中的条件得到满足则返回true
if(predicate.test(s))
highStudents.add(s);
}
return highStudents;
}


在调用时,针对不同判断条件可以将不同的predicate参数放入到参数中

//返回身高超过180cm的学生
List<Student> filteredStudents = studentFilter(students, new StudentHeightPredicate());
//返回身高超过90cm的学生
List<Student> filteredStudents = studentFilter(students, new StudentAvgScorePredicate());


如果我们还需要根据其它条件返回满足条件的学生,那么我们只需要建立新的类去实现StudentPredicate接口即可,无需复制粘贴原有代码,实现了以抽象应对改变

但是我们还不能高兴地太早,有的同学可能会发现,为了判断是否满足条件的代码当作参数传递给方法,我们现在不得不建立多个实现了StudentPredicate接口的类。

如果这些方法只被调用一两次,那么这些新被建立的类就显得有些啰嗦了。

还好,Java的匿名类机制可以帮助我们免于建立过多的只被使用一两次的类。

简单来说,Java的匿名类允许我们在声明类的同时对它进行实例化。也就是说我们不需要建立StudentHeightPredicate和StudentAvgScorePredicate这两个类了,而只需要:

//返回身高超过180cm的学生
List<Student> filteredStudents = studentFilter(students, new StudentPredicate() {
@Override
public boolean test(Student s) {
if(s.getHeight()>=180)
return true;
return false;
}
});

//返回身高超过90cm的学生
List<Student> filteredStudents = studentFilter(students, new StudentPredicate() {
@Override
public boolean test(Student s) {
if(s.getAvgScore()>=90)
return true;
return false;
}
});


看到这里同学们可能就要问了,这匿名类的机制也不是Java 8的新特性呀,为什么要在这里大费周章地讲解呢?

因为在Java 8中提供的Lambda表达式能够将上面的代码大大简化为:

List<Student> filteredStudents2 = studentFilter(students,
Student s -> s.getHeight()>=180
);


是不是代码变得相当简捷,干净了?关于Lambda表达式的详解,请见博主的下一篇博客。

利用Java的泛型机制,我们还可以将我们的StudentPredicate接口在抽象的道路上走得更远。

首先再回忆一下我们的StudentPredicate

interface StudentPredicate{
//由于我们需要满足的条件是学生的属性,因此传入的参数直接写为Student实例即可
boolean test(Student s);
}


这样写接口明显不够抽象,我们能够利用泛型写出一个更加抽象的接口:

interface Predicate<T>{
//这里使用泛型来传入对象
boolean test(T t);
}


StudentFilter 也可以相应地修改为引入泛型的Filter方法

public static List<Student> studentFilter(List<Student> students, StudentPredicate predicate){
List<Student> highStudents = new ArrayList<>();
for(Student s : students) {
//如果谓词方法中的条件得到满足则返回true
if(predicate.test(s))
highStudents.add(s);
}
return highStudents;
}


修改为

public static <T> List<T> filter(List<T> list, Predicate predicate){
List<T> result = new ArrayList<>();
for(T t : list) {
//如果谓词方法中的条件得到满足则返回true
if(predicate.test(t))
result.add(t);
}
return result;
}


有了这样的超级抽象版的Predicate接口,我们再也不用担心,用户的需求是返回所有满足某些条件学生,或者是返回所有满足某些条件的老师了:

//返回所有均分90以上的学生 匿名类写法
List<Student> filteredStudents = filter(students, new Predicate(){

@Override
public boolean test(Student s) {
if(s.getAvgScore()>=90)
return true;
return false;
}
});

//返回所有均分90以上的学生 Lambda表达式写法
List<Student> filteredStudents = filter(students,
Student s -> s.getAvgScore()>=90
);

//返回所有工资5000以上的老师 匿名类写法
List<Teacher> filteredTeachers = filter(teachers, new Predicate(){

@Override
public boolean test(Teacher t) {
if(t.getAvgSalary()>=5000)
return true;
return false;
}
});

//返回所有工资5000以上的老师 Lambda表达式写法
List<Teacher> filteredTeachers = filter(teachers,
Teacher t -> t.getAvgSalary()>=5000
);


总结:

行为参数化,就是一个方法接受多个不同的行为作为参数,并在内部使用它们,完成不同行为的能力

行为参数化可以让代码更好地适应不断变化的需求,减轻未来的工作量

传递代码,就是将新行为作为参数传递给方法。但在Java 8之前实现起来很啰嗦。为接口声明的许多只用一次的实体类而造成的啰嗦代码,在Java 8之前可以用匿名类消除
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息