Java8提出了三个新概念:流处理、行为参数化、并行处理。
目录
什么是行为参数化?
行为参数化的好处是什么?
应对不断变化的需求的案例?
1. 什么是行为参数化?
行为是一个动词,指的是举止动作;参数是个名称,是一个变量;将一个“举止动作”作为参数,传递给方法。
那么行为参数化就是一个方法接受多个不同的行为作为参数,并在内部使用它们,完成不同的行为能力。
2. 行为参数化的好处是什么?
行为参数化可以让代码更好的应用不断变化的需求,减少未来的工作量,减少冗余的代码。
所谓传递代码,就是将新行为作为参数传递给方法。在Java8之前需要为接口声明许多只用一次的实体类,实现起来很啰嗦。在Java8之前可以用匿名类来减少这种啰嗦的代码。
3. 应对不断变化的需求的案例
编写能够应对变化的需求的代码并不容易,让我们来看一个例子,我们会逐渐改进这个例子。以展示一些让代码更灵活的最佳做法。
Alice是一个农场的农夫,你需要帮他实现一个从列表中筛选绿苹果的功能,听起来很简单吧。
3.1 小试牛刀,第一个解决方案可能是下面这样的
public static List<Apple> filterGreenApples(List<Apple> inventory) {
List<Apple> result = new ArrayList<Apple>();
for (Apple apple : inventory) {
if ("green".equals(apple.getColor()) {
result.add(apple);
}
} return result;
}
现在Alice改变注意了,他还想要筛选红苹果,你该怎么做呢?简单的解决当大就是复制这个方法,把名字改成filterRedApples,然后更改if条件匹配红苹果。然而Alice想要筛选多种苹果:浅绿色、暗红色、黄色…这种做法就应付不了了。一个良好的原则是在编写类似的代码后,尝试将其抽象化。
3.2 再展身手,把颜色作为参数
public static List<Apple> filterApplesByColor(List<Apple> inventory,
String color) {
List<Apple> result = new ArrayList<Apple>();
for (Apple apple : inventory) {
if (apple.getColor().equals(color)) {
result.add(apple);
}
}
return result;
}
Alice又跑来跟你说,要是能区分轻苹果和重苹果就太好了,重苹果一般是150g。作为软件工程师,你早就想到Alice可能还会更改重量,于是你是这样解决的:
public static List<Apple> filterApplesByWeight(List<Apple> inventory,
int weight) {
List<Apple> result = new ArrayList<Apple>();
For(Apple apple:inventory){
if (apple.getWeight() > weight) {
result.add(apple);
}
}
return result;
}
解决方案不错,但是请注意,你复制了大部分的代码来实现遍历库存,并对每一个苹果应用筛选条件。这有点令人失望,应为它打破了DRY(Don’t Repeat Yourself,不要重复你自己)的软件原则。
3.3 第三次尝试:对你想到的每一个属性做筛选
public static List<Apple> filterApples(List<Apple> inventory, String color,
int weight, boolean flag) {
List<Apple> result = new ArrayList<Apple>();
for (Apple apple : inventory) {
if ((flag && apple.getColor().equals(color)) ||
(!flag && apple.getWeight() > weight)) {
result.add(apple);
}
}
return result;
}
//你可以这么调用:
List<Apple> greenApples = filterApples(inventory, "green", 0, true);
List<Apple> heavyApples = filterApples(inventory, "", 150, false);
这种做法好吗?目前只是颜色和重量两个属性的判断,如果需要过滤掉生产地、形状... 属性呢?你会有好多个重复的filter方法,或一个巨大的非常复杂的方法。对与组合属性的扩展性好像不太友好。
行为参数化
让我们后退一步来看看更高层次的抽象。一种可能的解决方案是对你的选择标准建模:你考虑的是苹果,需要根据Apple的某些属性(比如绿色、重量)来返回一个boolean值。我们称它为谓词(一个返回boolean值的函数),让我们定义一个接口为选择标准建模。
public interface ApplePredicate {
boolean test(Apple apple);
}
现在你可以用ApplePredicate的多个实现代表不同的选择标准了。
仅仅选出重的苹果
public class AppleHeavyWeightPredicate implements ApplePredicate {
public boolean test(Apple apple) {
return apple.getWeight() > 150;
}
}
仅仅选出绿苹果
public class AppleGreenColorPredicate implements ApplePredicate {
public boolean test(Apple apple) {
return "green".equals(apple.getColor());
}
}
你可以把这些看作是filter方法的不同行为。这些行为就像是不同的策略,ApplePredicate就像是一个算法蔟。
但是,该怎么利用ApplePredicate的不同实现呢?你需要让filterApples方法接受ApplePredicate对象,对Apple做条件筛选。这就是行为参数化:让方法接受多种行为做参数,并在内部使用,来完成不同的行为。
3.4 第四次尝试:根据抽象条件筛选
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;
}
现在你把filterApples方法迭代集合的逻辑与你要应用到集合中每个元素的行为区分开了。
现在Alice让你找出所有重量超过150g的红苹果,你只需要创建一个类来实现ApplePredicate就行了。
public class AppleRedAndHeavyPredicate implements ApplePredicate {
public boolean test(Apple apple) {
return "red".equals(apple.getColor())
&& apple.getWeight() > 150;
}
}
List<Apple> redAndHeavyApples = filterApples(inventory, new AppleRedAndHeavyPredicate());
你已经做了一件很酷的事:filterApples方法的行为取决你通过ApplePredicate对象传递的代码,换句话说,你把filterApples方法的行为参数化了!
请注意,在上一个例子中,唯一重要的代码是test方法的实现,由于filterApples方法只能接受对象,所以你必须把代码包裹ApplePredicate对象里。你的做法就类似与在内联“传递代码”。你会在下一节中看到,通过使用Lambda,可以直接将表达式"red".equals(apple.getColor())
&&apple.getWeight() > 150传递给filterApples方法,省去定义多个ApplePredicate的实现类,从而去掉不必要的代码。
Java8之前是如何做的?使用匿名类
3.5 第五次尝试:使用匿名类
List<Apple> redApples = filterApples(inventory, new ApplePredicate() {
public boolean test(Apple apple) {
return "red".equals(apple.getColor());
}
});
Java8是如何做的?使用Lambda表达式
3.6 第六次尝试:使用Lambda表达式
List<Apple> result = filterApples(inventory, (Apple apple) -> "red".equals(apple.getColor()));
匿名类与Lambda表达式的比较
匿名类:
它看起来很笨重,因为它占用了很多空间。
让我们觉得它用起来很让人费解。好的代码应该是一目了然的。虽然匿名类在一定程度上改善了为一个接口声明好几个实体类的啰嗦问题,但它仍不能令人满意。
Lambda表达式:
不得不承认这代码看上去比先前干净很多。
3.7 第七次尝试:将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的列表上了。
eg.
List<Apple> redApples =
filter(inventory, (Apple apple) -> "red".equals(apple.getColor()));
List<Integer> evenNumbers =
filter(numbers, (Integer i) -> i % 2 == 0);
酷不酷?你现在在灵活性和简洁性之间找到了最佳的平衡点,这在Java8之前是不能做到的!
3.8 真实的例子
用Comparator来排序
Alice想要根据苹果的重量对库存进行排序(ps,Java8中,List自带了一个sort方法)我们用Collections.sort
// java.util.Comparator
public interface Comparator<T> {
public int compare(T o1, T o2);
}
inventory.sort(new Comparator<Apple>() {
public int compare(Apple a1, Apple a2){
return a1.getWeight().compareTo(a2.getWeight());
}
});
用Lambda表达式的话,看起来是这样的:
inventory.sort(
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
用 Runnable 来执行代码块
// java.lang.Runnable
public interface Runnable{
public void run();
}
Thread t = new Thread(new Runnable() {
public void run(){
System.out.println("Hello world");
}
});
用Lambda表达式的话,看起来是这样的:
Thread t = new Thread(() -> System.out.println("Hello world"));
看完这个案例,你对行为参数化有所了解了吗?😊