一、通过行为参数化传递代码
1.1 应对不断变化的需求
(1)筛选绿苹果
第一个解决方案可能是下面这样的:
public static List filterGreenApples(List inventory) {
List result = new ArrayList<>();
for (Apple apple : inventory) {
if ("green".equals(apple.getColor())) {
result.add(apple);
}
}
return result;
}
然而,要是农名想要筛选多种颜色呢,这种方法就应付不了了,一个良好的原则就是在编写类似的代码之后,尝试将其抽象化。
(2)把颜色作为参数
public static List filterAppleByColor(List inventory, String color) {
List result = new ArrayList<>();
if ("".equals(color) || color == null) {
return result;
}
for (Apple apple : inventory) {
if (color.equals(apple.getColor())) {
result.add(apple);
}
}
return result;
}
现在,只要像下面这样调用方法,农名朋友就会满意了:
List greens = filterAppleByColor(inventory, "grren");
List blue = filterAppleByColor(inventory, "blue");
然而,这时候农名伯伯又跑回来跟你说:“要是能区分轻的苹果和重的苹果就太好了,重的苹果一般是重量大于150克”。
然后,你修改了方法的入参,写了一个如下的方法用来筛选重量:
public static List filterAppleByWeight(List inventory, int weight) {
List result = new ArrayList<>();
for (Apple apple : inventory) {
if (apple.getWeight() > weight) {
result.add(apple);
}
}
return result;
}
但是,你复制了大量的代码,从工程工作量的角度来看,这代价太大了。
(3)对你能想到的每个属性做筛选
这时候,你就想,把所有需要做筛选的参数作为方法入参不就行了,于是,你写出了如下的代码:
public static List filterAppleByWeight(List inventory, String color, int weight, boolean flag) {
List result = new ArrayList<>();
for (Apple apple : inventory) {
if ((flag && apple.getColor().equals(color)) || (!flag && apple.getWeight() > weight)) {
result.add(apple);
}
}
return result;
}
但是,如果这时候农民修改了需求,你需要无休止的修改方法,这时候,你需要一种更好的方法来解决这些问题。
1.2 行为参数化
(1)传递代码/行为
首先,让我们定义一个接口来对选择标准建模:
public interface ApplePredicate {
boolean test(Apple apple);
}
利用ApplePredicate改过之后,filter方法看起来是这样的:
public static List filterApples(List inventory, ApplePredicate p) {
List result = new ArrayList<>();
for (Apple apple : inventory) {
if (p.test(apple)) {
result.add(apple);
}
}
return result;
}
这时候,如果农民想要找出所有重量超过150克的苹果,你只需要创建一个类来实现ApplePredicate接口即可,如下所示:
public class AppleRedAndWeightPredicate implements ApplePredicate {
@Override
public boolean test(Apple apple) {
return "red".equals(apple.getColor()) && apple.getWeight() > 150;
}
}
这时候,我们其实已经把方法的行为参数化了。
(2)多种行为,一个参数
1.3 对付啰嗦
(1)匿名类
匿名类和java局部类差不多,但匿名类没有名字,它允许你同时声明并实例化一个类,换句话说,它允许你随用随建。
(2)使用匿名类
List redApples = filterApples(inventory, new ApplePredicate() {
@Override
public boolean test(Apple apple) {
return "red".equals(apple.getColor());
}
});
但是匿名类还是不够好,第一,它往往很笨重,因为它占用了很多空间,第二,很多人觉得它用起来很令人费解。
(3)使用Lambda表达式
上面的代码用Lambda改写如下:
List result = filterApples(inventory, (Apple apple) -> "red".equals(apple.getColor()));
(4)将List类型抽象化
目前,filterApples方法还只适用于Apple,你还可以将List类型抽象化,从而超越你眼前要处理的问题,即将Apple使用泛型代替。
1.4 真实的例子
(1)用Comparator来排序
你可以使用匿名类,按照重量升序对库存排序:
inventory.sort(new Comparator() {
@Override
public int compare(Apple o1, Apple o2) {
return o1.getWeight().compareTo(o2.getWeight());
}
});
或者使用Lambda表达式来改写:
inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
(2)用Runnable执行代码块
使用匿名类:
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello world");
}
});
使用Lambda表达式:
Thread thread1 = new Thread(()-> System.out.println("hello world"));
二、Lambda表达式
2.1 Lambda管中窥豹
Lambda表达式主要由三部分构成:参数、箭头、Lambda主体。如下所示:
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight())
2.2 在哪里以及如何使用Lambda
(1)函数式接口
函数式接口就是只定义了一个抽象方法的接口,Lambda表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例。
(2)函数描述符