人们不愿意用那些很麻烦的功能或概念。目前,当要把新的行为传递给filterApples
方法的时候,不得不声明好几个实现ApplePredicate
接口的类,然后实例化好几个只会提到一次的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());
}
}
public class FilteringApples {
public static void main(String...args) {
List<Apple> inventory = Arrays.asList(new Apple(80,"green"), new Apple(155,"green"), new Apple(120,"red"));
//结果是一个包含一个155克Apple的List
List<Apple> heavyApples = filterApples(inventory, new AppleHeavyWeightPredicate());
//结果是一个包含两个绿Apple的List
List<Apple> greenApples = filterApples(inventory, new AppleGreenColorPredicate());
}
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;
}
}
能不能做得更好呢?Java
有一个机制称为匿名类,它可以同时声明和实例化一个类。它可以帮助进一步改善代码,让它变得更简洁。但这也不完全令人满意。
匿名类和Java局部类(块中定义的类)差不多,但匿名类没有名字。它允许同时声明并实例化一个类。换句话说,它允许随用随建。
下面的代码展示了如何通过创建一个用匿名类实现ApplePredicate的对象,重写筛选的例子:
//直接内联参数化filterapples方法的行为
List<Apple> redApples = filterApples(inventory, new ApplePredicate() {
public boolean test (Apple apple) {
return "red".equals(apple.getcolor());
}
});
GUI应用程序中经常使用匿名类来创建事件处理器对象(下面的例子使用的是JavaFX API
,一种现代的Java UI
平台):
button.setOnAction(new EventHandlercActionEvent>() {
public void handle(ActionEvent event) {
System.out.println("Woooo a click!!");
}
});
但匿名类还是不够好。第一,它往往很笨重,因为它占用了很多空间。还拿前面的例子来看,如下面高亮的代码所示:
//很多模板代码
List<Apple> redApples = filterApples(inventory, new ApplePredicate() {
public boolean test(Apple a) {
return "red".equals(a.getcolor());
}
});
button.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
System.out.println("Woooo a click!!");
}
});
第二,很多程序员觉得它用起来很让人费解。比如,如下展示的一个经典的Java谜题,它让大多数程序员都措手不及:
下面的代码执行时会有什么样的输出呢,4、5、6还是42?
public class MeaningofThis
{
public final int value = 4;
public void doIt()
{
int value = 6;
Runnable r = new Runnable() {
public final int value = 5;
public void run() {
int value = 10;
System.out.println(this.value);
}
};
r.run();
}
public static void main(String...args)
{
MeaningOfThis m = new MeaningofThis();
//这一行的输出是什么?
m.doIt();
}
}
答案是5,因为this指的是包含它的Runnable,而不是外面的类MeaningOfThis。
整体来说,啰嗦就不好;它让人不愿意使用语言的某种功能,因为编写和维护啰嗦的代码需要很长时间,而且代码也不易读。好的代码应该是一目了然的。即使匿名类处理在某种程度上改善了为一个接口声明好几个实体类的啰嗦问题,但它仍不能令人满意。在只需要传递一段简单的代码时(例如表示选择标准的boolean表达式),你还是要创建一个对象,明确地实现一个方法来定义一个新的行为(例如Predicate中的test方法或是EventHandler中的handler方法)。
在理想的情况下,我们想鼓励程序员使用行为参数化模式,因为正如你在前面看到的,它让代码更能适应需求的变化。在第3章中,你会看到Java8的语言设计者通过引入Lambda表达式——一种更简洁的传递代码的方式——解决了这个问题。好了,悬念够多了,下面简单介绍一下Lambda表达式是怎么让代码更干净的。
上面的代码在Java 8
里可以用Lambda
表达式重写为下面的样子:
List<Apple> result = filterApples(inventory, (Apple apple) -> "red".equals(apple.getcolor()));
不得不承认这代码看上去比先前干净很多。这很好,因为它看起来更像问题陈述本身了。现在已经解决了啰嗦的问题。下图对到目前为止的工作做了一个小结。
在通往抽象的路上,还可以更进一步。目前,filterApples
方法还只适用于Apple
。还可以将List
类型抽象化,从而超越眼前要处理的问题:
public interface Predicate<T> {
boolean test (T 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
表达式的例子:
List<Apple> redApples = filter(inventory,(Apple apple) -> "red".equals(apple.getcolor()));
List<Integer> evenNumbers = filter(numbers, (Integer i) -> i % 2 == 0);
现在在灵活性和简洁性之间找到了最佳平衡点,这在Java 8
之前是不可能做到的。
现在已经看到,行为参数化是一个很有用的模式,它能够轻松地适应不断变化的需求。这种模式可以把一个行为(一段代码)封装起来,并通过传递和使用创建的行为(例如对Apple
的不同谓词)将方法的行为参数化。前面提到过,这种做法类似于策略设计模式。Java API
中的很多方法都可以用不同的行为来参数化。这些方法往往与匿名类一起使用。展示三个例子:用一个Comparator
排序,用Runnable
执行一个代码块,以及GUI
事件处理。
对集合进行排序是一个常见的编程任务。比如,那位农民朋友想要根据苹果的重量对库存进行排序,或者他可能改了主意,希望根据颜色对苹果进行排序。听起来有点儿耳熟?是的,需要一种方法来表示和使用不同的排序行为,来轻松地适应变化的需求。
在Java8
中,List
自带了一个sort
方法(也可以使用Collections.sort
)。sort
的行为可以用java.util.Comparator
对象来参数化,它的接口如下:
// java.util.Comparator
public interface Comparator<T> {
public int compare(T o1, T o2);
}
因此,可以随时创建Comparator
的实现,用sort
方法表现出不同的行为。比如,可以使用匿名类,按照重量升序对库存排序:
inventory.sort (new Comparator<Apple>() {
public int compare(Apple al, Apple a2) {
return al.getWeight().compareTo(a2.getWeight());
}
});
如果农民改了主意,可以随时创建一个Comparator
来满足他的新要求,并把它传递给sort
方法。而如何进行排序这一内部细节都被抽象掉了。用Lambda
表达式的话,看起来就是这样:
inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight())));
现在暂时不用担心这个新语法,之后会详细讲解如何编写和使用Lambda
表达式。