最近在开发的过程中有一个点让我比较感兴趣,就是使用Lambda表达式的方式来实现Comparator接口。
1. 关于Comparator和Comparable
既然提到了Comparator,那就大致来说一下Comparator和Comparable接口的区别。
Comparator是一种策略模式,即被比较的对象自身不需要做任何改变(实现任何排序接口),而是通过实例化一个Comparator策略来实现对象之间的排序。
Comparable支持对象自身进行改变(对象自身实现Comparable接口),从而具有排序功能。
举个例子来说,一组对象需要用Collection的sort方法进行排序,有两种方法,要么是对象实现Comparable接口,自身具有可排序属性;要么是实现一个Comparator比较器,在调用sort方法时传入。
下面再来说一说Lambda表达式。
2. Lambda表达式
我们都知道,Lambda表达式是Java里面的函数式编程,那么在使用Lambda表达式首先要满足这样一个条件:首先他的类型是interface,而且有且仅有一个抽象方法。那么下面我们先来看一个使用Lambda表达式的例子。
先看一下Java7中的表达:
public class LambdaTest {
public static void main(String[] args) {
Say test1 = new Say() {
@Override
public void saySomthing(String something) {
System.out.println(something);
}
};
test2.saySomthing("hello");
}
interface Say {
void saySomthing(String something);
}
使用Lambda表达式后的Java8中的表达:
public class LambdaTest {
public static void main(String[] args) {
Say test2 = System.out::println;
test2.saySomthing("hello");
}
}
interface Say {
void saySomthing(String something);
}
那么类似的,我们要实现一个Comparator比较器会这样写:
public class LambdaTest {
public static void main(String[] args) {
Comparator<Book> bookComparator = Comparator.comparing(Book::getPrice);
Book book1 = new Book("a", 1);
Book book2 = new Book("b", 1);
System.out.println(bookComparator.compare(book1, book2));
}
}
class Book {
private String name;
private Integer price;
// 省略constructor/getter/setter
}
这样写完全OK,可是我们去看一下Comparator接口的源码,可能会觉得略微有点诧异。
可以注意到Comparator接口的源码有这样几个点:
- @FunctionalInterface注解的作用
- Comparator接口里有多个方法(两个抽象方法和多个default方法)
- default关键字的含义
下面逐一来解释一下上面这三个点。
(1) @FunctionalInterface注解的作用
@FunctionalInterface标注在一个接口上,说明这个接口是一个函数式接口。
那么关于函数式接口,有如下特点:
- 有且只有一个抽象方法
- 可以有多个静态方法
- 可以有多个default方法(默认方法)
- 可以有多个Object的public的抽象方法
(2) Comparator接口里有多个方法(两个抽象方法和多个default方法)
可以看到Comparator接口里除了有compare这个抽象方法,还有一个equals抽象方法,但是如上所说,函数式接口里允许有Object的public的抽象方法。
(3) default关键字的含义
default关键字修饰的接口方法可以有默认的方法体。当接口的实现类实现接口的时候,可以不去实现default关键字修饰的方法。
这样做是为了解决这样一种场景:
假设有一个接口定义如下:
interface TestInterFace {
void doSomething();
}
在一个应用中有该接口的大量实现类,可是突然这个接口里面需要新加一个方法doAnother()。此时,我们要在应用所有实现了这个接口的类中加上doAnother()的实现,这样会导致两个问题:一是修改的幅度对比较大;二是不是所有的实现类都需要去实现doAnother()方法。
default关键字就是为了解决这样的问题。在doAnother()方法前加上default关键字,之前的接口实现类不去实现doAnother()方法也不会报错。如果新的实现类实现这个方法,就等于该实现类覆盖了这个方法,这样最终的运行结果也是符合Java的多态性的。
但是需要注意的是,default关键字也不建议随便使用。
假设有两个接口:InterfaceA和InterfaceB,他们都有一个default void test()方法,现在又Class C同时实现InterfaceA和InterfaceB,在调用test()方法时就会出错,因为这样会引起二义性,编译器无法知道C调用的究竟是那一个test()方法。