通过行为参数化传递代码
通过行为参数化传递代码
——java 8 in action
文章目录
本章内容:
应对不断变化的需求
行为参数化
匿名类
Lamdba表达式预览
真实示例:Comparator、Runable和GUI
应对不断变化的需求
筛选绿苹果
第一个解决方案
public static List<Apple> filterGreenApples(List<Apple> inventory) {
List<Apple> result = new ArrayList<>();
for(Apple apple : inventory) {
if("green".equals(app.getColor()) {
result.add(apple);
})
}
return result;
}
将颜色作为参数
public static List<Apple> filterApplesByColor(List<Apple> inventory, String color) {
List<Apple> result = new ArrayList<>();
for(apple.getColor().equals(color)) {
result.add(apple);
}
return result;
}
现在只需像这样调用
List<Apple> greenApples = filterApplesByColor(inventory, "green");
List<Apple> redApples = filterApplesByColor(inventory, "red");
同样的增加重量
public static List<Apple> filterApplesByWeight(List<Apple> inventory, int weight) {
List<Apple> result = new ArrayList<>();
for(apple.getWeight() > weight) {
result.add(apple);
}
return result;
}
解决方案不错,但是它复制了大部分代码来实现遍历库存,并对每个苹果应用筛选条件,它打破了DRY(Don’t Repeat YourSelf)工程原则。若要改变筛选遍历的方式来提升性能,那么需要修改所有方法的实现。
通过添加标志位将筛选重量和颜色合并为一个方法
对能想到的每个属性做筛选
public static List<Apple> filterApples(List<Apple> inventory, String color, int weight, boolean flag) {
List<Apple> result = new ArrayList<>();
for(Apple apple : inventory){
if((flag && apple.getColor().equals(color) || (!flag && app.getWeight() > weight))){
result.add(apple);
}
}
return result;
}
你可以这么用List<Apple> greenApples = filterApples(inventory, "green", 0, true)
但是看上去糟透了,可读性太差,不能很好的应对变化的需求。
行为参数化
我们需要更高层次的抽象/一种可能的解决方案是对选择标准建模:根据Apple的某些属性来返回一个boolean的值。我们把它称之为一个谓词(即返回boolean值的函数)。定义一个标准来对选择标准进行建模。
public interface ApplePredicate {
boolean test(Apple apple);
}
public class AppleHeavyWeightPredicate implements ApplePredicate {
//仅仅选出重的苹果
public boolean test(Apple apple) {
return apple.getWeight() > 150;
}
}
public class AppleGreenPredicate implements ApplePredicate {
//仅仅选出绿苹果
public boolean test(Apple apple) {
return "green".equals(apple.getColor());
}
}
可以将刚刚做的看作filter方法的不同行为。和"策略设计模式相关",让你定义一族算法,将它们封装起来(称为“策略”),然后在运行时选择一个算法。
根据抽象条件进行筛选
public static List<Apple> filterApples(List<Apple> inventory, ApplePredicate p) {
List<Apple> result = new ArrayList<>();
for(Apple apple : inventory){
if(p.test(apple)){
result.add(apple);
}
}
return result;
}
- 传递代码行为
这段代码比第一次尝试的时候灵活很多,例如找出重量超过150g的红苹果只需要创建ApplePredicate就行了。
public class AppleRedAndHeavyPredicate implements ApplePredicate {
public boolean test(Apple apple) {
return "red".equals(app.getColor()) && apple.getWeight() > 150;
}
}
List<Apple> redAndHeavyApples = filterApples(inventory, new AppleRedAndHeavyPredicate);
在这个例子中唯一重要的代码是test方法的实现,正是它定义了filterApples方法的新行为。但是由于filterApples方法只能接受对象,所以必须把代码包裹在AoolePredicate对象中。通过使用Lambda,可以直接把表达式"red".equals(app.getColor()) && apple.getWeight() > 150;
传递个filterApples方法,而无需定义多个ApplePredicate类,从而去掉不必要的代码
- 多种行为,一个参数
行为参数化的好处在于你可以把迭代要筛选的集合的逻辑与对几何中每个元素应用的行为区分开来。这样可以重读使用同一个方法,给它不同的行为来达到不同的目的。
虽然可以将行为抽象出来,让代码适应需求的变化,但是这个过程很罗嗦,因为需要声明很多只要实例化一次的类。
对付啰嗦
匿名类
匿名类与java局部类(块中定义的类)差不多,但匿名类没有名字,它允许你同时声明并实例化一个类。换句话说,它允许你随用随建。
使用匿名类
使用匿名类重写筛选的例子:
List<Apple> redApples = filterApples(inventory, new AppPredicate() {
public boolean = (Apple apple) {
return "red".equals(apple.getColor());
}
})
GUI应用程序经常使用匿名类来创建事件处理对象(下面的例子使用的是javaFX API, 一种现代化的javaUI平台)
button.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActonEvent event){
System.out.println("Wooo a click!");
}
})
使用匿名类还是不够好。第一,它往往很笨重,因为它占用了很多空间。第二,很多程序员觉得它用起来很让人费解。
使用Lambda表达式
上面的代码在Java8中可以用Lambda表达式重写为下面的样子:List<Apple> result = filterApple(inventory, (Apple apple) -> "red".equals(apple.getColor()))
将List类型抽象化
在通往抽象的路上还可以更进一步。目前filterApples方法还只适用于Apple。你还可以将List类型抽象化,从而超越你眼前要处理的问题:
public interface Predicate<T> {
boolean test(T t);
}
public static <T> List<T> filter(List<T> list, Predicate<T> p) {
List<T> result = new ArrayList<>();
for(T e : list) {
if(p.test(e)) {
result.add(e);
}
}
return result;
}
现在可以将filter方法用在香蕉、橘子、Integer或是String的列表上了。这里有一个使用Lambda表达式的子:
真实的例子
用Comparator来排序
public interface Comparator<T> {
public int compare(T o1, To2);
}
创建Comparator的实现,用sort方法表现不同的行为:
inventory.sort((Apple a1, Apple a2) {
return a1.getWeight().compareTo(a2.getWeight);
})
用Lamdba表达式的话,看起来像这样:inventory.sort(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
用Runable执行代码块
public interface Runnable {
public void fun();
}
可以向下面这样,使用这个接口创建执行不同行为的线程:
Thread t = new Thread(new Runnable() {
public void run() {
System.out.println("hello world")
}
})
用Lambda的话,看起来像这样:Thread t = new Thread(() -> System.out.println("hello world"));
GUI事件处理
Button button = new Button("Send");
button.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
label.setText("Send!!");
}
})
这里setOnAction方法的行为就用EventHandler参数化了。用Lambda表达式的话。看起来像这样:button.setOnAction((ActionEvent event) -> label.setText("Send!!"))