2.4 真实的例子

你现在已经看到,行为参数化是一个很有用的模式,它能够轻松地适应不断变化的需求。这种模式可以把一个行为(一段代码)封装起来,并通过传递和使用创建的行为(例如对Apple的不同谓词)将方法的行为参数化。前面提到过,这种做法类似于策略设计模式。你可能已经在实践中用过这个模式了。Java API中的很多方法都可以用不同的行为来参数化。这些方法往往与匿名类一起使用。我们会展示四个例子,这应该能帮助你巩固传递代码的思想了:用一个Comparator排序,用Runnable执行一个代码块,用Callable从任务返回结果,以及GUI事件处理。

2.4.1 用Comparator来排序

对集合进行排序是一个常见的编程任务。比如,你的那位农民朋友想要根据苹果的重量对库存进行排序,或者他可能改了主意,希望你根据颜色对苹果进行排序。听起来有点儿耳熟?是的,你需要一种方法来表示和使用不同的排序行为,以轻松地适应变化的需求。

在Java 8中,List自带了一个sort方法(你也可以使用Collections.sort)。sort的行为可以用java.util.Comparator对象来参数化,它的接口如下:

  1. // java.util.Comparator
  2. public interface Comparator<T> {
  3. int compare(T o1, T o2);
  4. }

因此,你可以随时创建Comparator的实现,用sort方法表现出不同的行为。例如,你可以使用匿名类,按照重量升序对库存排序:

  1. inventory.sort(new Comparator<Apple>() {
  2. public int compare(Apple a1, Apple a2) {
  3. return a1.getWeight().compareTo(a2.getWeight());
  4. }
  5. });

如果农民改了主意,你可以随时创建一个Comparator来满足他的新要求,并把它传递给sort方法。而如何进行排序这一内部细节都被抽象掉了。用Lambda表达式的话,看起来就是这样:

  1. inventory.sort(
  2. (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));

现在暂时不用担心这个新语法,下一章会详细讲解如何编写和使用Lambda表达式。

2.4.2 用Runnable执行代码块

使用Java的线程,一块代码可以与程序的其他部分并发执行。但是,怎么才能通知线程执行哪块代码呢?此外,几个线程可能还需要执行不同的代码。我们需要一种方式来表示哪一段代码会在之后执行。Java 8之前,能传递给线程结构的只有对象,因此之前典型的使用模式是传递一个带有run方法,返回值为void(即不返回任何对象)的匿名类,非常臃肿。这种匿名类通常会实现一个Runnable接口。

在Java里,你可以使用Runnable接口表示一个要执行的代码块。请注意,该代码不会返回任何结果(即void):

  1. // java.lang.Runnable
  2. public interface Runnable{
  3. void run();
  4. }

你可以像下面这样,使用这个接口创建执行不同行为的线程:

  1. Thread t = new Thread(new Runnable() {
  2. public void run(){
  3. System.out.println("Hello world");
  4. }
  5. });

用Lambda表达式的话,看起来是这样:

  1. Thread t = new Thread(() -> System.out.println("Hello world"));

2.4.3 通过Callable返回结果

你可能已经非常熟悉Java 5引入的ExecutorServiceExecutorService接口解耦了任务的提交和执行。与使用线程和Runnable的方式比较起来,通过ExecutorService你可以把一项任务提交给一个线程池,并且可以使用Future获取其执行的结果,这种方式用处非常大。不必担心你对此一无所知,我们会在之后讨论并发的章节中详细介绍这部分内容。目前你只需要知道使用Callable接口可以对返回结果的任务建模。你可以把它看成升级版的Runnable

  1. // java.util.concurrent.Callable
  2. public interface Callable<V> {
  3. V call();
  4. }

你可以像下面这样使用它,即提交一个任务给ExecutorService。下面这段代码会返回执行任务的线程名:

  1. ExecutorService executorService = Executors.newCachedThreadPool();
  2. Future<String> threadName = executorService.submit(new Callable<String>() {
  3. @Override
  4. public String call() throws Exception {
  5. return Thread.currentThread().getName();
  6. }
  7. });

如果使用Lambda表达式,上述代码可以更加简化,如下所示:

  1. Future<String> threadName = executorService.submit(
  2. () -> Thread.currentThread().getName());

2.4.4 GUI事件处理

GUI编程的一个典型模式就是执行一个操作来响应特定事件,如鼠标单击或在文本上悬停。例如,如果用户单击“发送”按钮,你可能想显示一个弹出式窗口,或把行为记录在一个文件中。你还是需要一种方法来应对变化。你应该能够作出任意形式的响应。在JavaFX中,你可以使用EventHandler,把它传给setOnAction来表示对事件的响应:

  1. Button button = new Button("Send");
  2. button.setOnAction(new EventHandler<ActionEvent>() {
  3. public void handle(ActionEvent event) {
  4. label.setText("Sent!!");
  5. }
  6. });

这里,setOnAction方法的行为就用EventHandler参数化了。用Lambda表达式的话,看起来就是这样:

  1. button.setOnAction((ActionEvent event) -> label.setText("Sent!!"));