Comparable

Comparable接口用于自定义比较规则。对于Java的原生类型,他们之间的大小比较是依照数学上的大小来比较,自然比较好理解。但是对于自定义的复合类型(通常来说是Java bean),Java怎么比较他们之间的大小呢?

比如说我们自定义了Apple这个类:

class Apple {
private String name;
private int weight;
// setter getter省略
}

Comparable接口正好用于这种场景。Comparable接口只有一个方法compareTo:

public interface Comparable {
public int compareTo(T o);
}

用于定义大小比较的逻辑。

compareTo方法的返回值含义如下:

小于0,当前对象小于比较对象

等于0,当前对象等于计较对象

大于0,当前对象大于比较对象

下面我们举一个完整的例子:

public class ComparableDemo {
public static void main(String[] args) {
List apples = new ArrayList<>();
apples.add(new Apple("a", 2));
apples.add(new Apple("b", 3));
apples.add(new Apple("c", 1));
apples.add(new Apple("d", 10));
apples.add(new Apple("e", 7));
Collections.sort(apples);
System.out.println(apples);
}
}
class Apple implements Comparable {
private String name;
private int weight;
public Apple() {
}
public Apple(String name, int weight) {
this.name = name;
this.weight = weight;
}
// setter getter 省略
@Override
public String toString() {
return "Apple{" +
"name='" + name + '\'' +
", weight=" + weight +
'}';
}
@Override
public int compareTo(Apple o) {
// 这里我们定义了Apple类型的大小比较逻辑,只比较weight字段
return this.weight - o.weight;
}
}

输出结果为:

[Apple{name='c', weight=1}, Apple{name='a', weight=2}, Apple{name='b', weight=3}, Apple{name='e', weight=7}, Apple{name='d', weight=10}]

我们发现Apple最终按照weight字段升序排列。

Comparator

Comparator的作用和Comparable基本相同。但是Comparator不和Java bean绑定,即这个接口不是让Java bean去实现的,而是在需要比较大小的场合(例如排序),作为大小比较规则传入。

我们仍以Apple这个类为例子。去掉Apple类实现的Comparable接口,如下所示:

class Apple {
private String name;
private int weight;
public Apple() {
}
public Apple(String name, int weight) {
this.name = name;
this.weight = weight;
}
// setter getter 省略
@Override
public String toString() {
return "Apple{" +
"name='" + name + '\'' +
", weight=" + weight +
'}';
}
}

我们接下来调用Collections.sort另一种重载方法:

public static void sort(List list, Comparator super T> c)

和上一个例子中的sort方法不同,这里的sort方法不要求T类型对象实现Comparable接口,但是需要传入一个Comparator比较器。

最终代码如下所示:

public class ComparatorDemo {
public static void main(String[] args) {
List apples = new ArrayList<>();
apples.add(new Apple("a", 2));
apples.add(new Apple("b", 3));
apples.add(new Apple("c", 1));
apples.add(new Apple("d", 10));
apples.add(new Apple("e", 7));
Collections.sort(apples, new Comparator() {
@Override
public int compare(Apple o1, Apple o2) {
// 和Comparable的写法基本类似
return o1.getWeight() - o2.getWeight();
}
});
System.out.println(apples);
}
}

注意:在这个例子中,Comparator的写法过于复杂,我们可以利用Comparator为我们提供的辅助方法来构建所需的Comparator。我们可以讲其简化如下:

Collections.sort(apples, Comparator.comparingInt(Apple::getWeight));

下面我们讲解下Comparator接口的辅助方法。

Comparator的辅助方法

reversed方法:将已有Comparator的逻辑颠倒,即原先的大于变小于,小于变大于。

default Comparator reversed() {
return Collections.reverseOrder(this);
}

thenComparing方法:用于串联多个Comparator,如果第一个Comparator比较的结果相等,则使用第二个Comparator进行比较

default Comparator thenComparing(Comparator super T> other) {
Objects.requireNonNull(other);
return (Comparator & Serializable) (c1, c2) -> {
int res = compare(c1, c2);
return (res != 0) ? res : other.compare(c1, c2);
};
}

Java官方给出的例子:

Comparator cmp = Comparator.comparingInt(String::length)
.thenComparing(String.CASE_INSENSITIVE_ORDER);

thenComparing还有几重载版本:

通过key提取器(Java bean到用于比较的字段)和key比较器构建:

default  Comparator thenComparing(
Function super T, ? extends U> keyExtractor,
Comparator super U> keyComparator)
{
return thenComparing(comparing(keyExtractor, keyComparator));
}

通过key提取器构建,这里要求key的类型必须实现了Comparable接口,因此不必再提供key比较器。

default > Comparator thenComparing(
Function super T, ? extends U> keyExtractor)
{
return thenComparing(comparing(keyExtractor));
}

对于常见的基本类型比较,Comparable接口提供了简化版的thenComparatingXXX方法,如下所示:

default Comparator thenComparingInt(ToIntFunction super T> keyExtractor) {
return thenComparing(comparingInt(keyExtractor));
}
default Comparator thenComparingLong(ToLongFunction super T> keyExtractor) {
return thenComparing(comparingLong(keyExtractor));
}
default Comparator thenComparingDouble(ToDoubleFunction super T> keyExtractor) {
return thenComparing(comparingDouble(keyExtractor));
}

naturalOrder方法:返回自然顺序比较结果(要求对象类型实现Comparable接口,比较逻辑为compareTo方法内容)

public static > Comparator naturalOrder() {
return (Comparator) Comparators.NaturalOrderComparator.INSTANCE;
}

nullsFirst:返回一个null友好的Comparator,不会因为比较对象为null,报出NullPointerException。

比较的逻辑为对于a和b两个待比较对象:

如果a为null,b也为null,则a和b相等

如果a为null,b不为null,则a > b

如果b为null,a不为null,则b > a

如果a和b都不为null,则按照参数comparator的逻辑再次比较

如果a和b都不为null,且参数comparator为null,则认为a和b相等

public static Comparator nullsFirst(Comparator super T> comparator) {
return new Comparators.NullComparator<>(true, comparator);
}

nullsLast:和nullFirst类似,不同的地方在于如果比较双方有一个为null,null的一方会认为较小,和nullsFirst逻辑相反。

public static Comparator nullsLast(Comparator super T> comparator) {
return new Comparators.NullComparator<>(false, comparator);
}

comparing:根据key提取器和key比较器生成一个新的Comparator

public static Comparator comparing(
Function super T, ? extends U> keyExtractor,
Comparator super U> keyComparator)
{
Objects.requireNonNull(keyExtractor);
Objects.requireNonNull(keyComparator);
return (Comparator & Serializable)
(c1, c2) -> keyComparator.compare(keyExtractor.apply(c1),
keyExtractor.apply(c2));
}

comparing另一版本:根据key比较器生成一个新的Comparator,要求Key的类型必须实现Comparable接口。

public static > Comparator comparing(
Function super T, ? extends U> keyExtractor)
{
Objects.requireNonNull(keyExtractor);
return (Comparator & Serializable)
(c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}

对于常见类型的比较器,Comparator提供了更为便捷的方式构建Comparator:

public static Comparator comparingInt(ToIntFunction super T> keyExtractor) {
Objects.requireNonNull(keyExtractor);
return (Comparator & Serializable)
(c1, c2) -> Integer.compare(keyExtractor.applyAsInt(c1), keyExtractor.applyAsInt(c2));
}
public static Comparator comparingLong(ToLongFunction super T> keyExtractor) {
Objects.requireNonNull(keyExtractor);
return (Comparator & Serializable)
(c1, c2) -> Long.compare(keyExtractor.applyAsLong(c1), keyExtractor.applyAsLong(c2));
}
public static Comparator comparingDouble(ToDoubleFunction super T> keyExtractor) {
Objects.requireNonNull(keyExtractor);
return (Comparator & Serializable)
(c1, c2) -> Double.compare(keyExtractor.applyAsDouble(c1), keyExtractor.applyAsDouble(c2));
}
Serializable

Serializable是一个Marker Interface(没有任何方法定义的Interface,用作标记)。

如果一个类型支持序列化,那么它需要实现Serializable接口。

Cloneable

和Serializable一样,也是一个Marker Interface。如果一个类可以通过调用Object的clone方法复制,则这个类必须实现Cloneable接口,否则会抛出CloneNotSupportedException异常。

Iterable

Java内置支持如下所示的foreach循环:

for (String s : someList) {
// ...
}

对于上面这个例子,Java怎么知道someList这个类型是如何被迭代的呢?答案藏在Iterable接口。somelist实现了Iterable接口。Iterable接口有一个iterator方法,要出返回一个适用于迭代该对象的迭代器。

下面我们举一个例子,我们自己实现一个最简单的集合类MyList,支持使用foreach方法迭代。扩容、删除元素和整理元素全都不考虑。代码如下:

// 实现Iterable接口
class MyList implements Iterable {
// 容量为10
private static final int CAPACITY = 10;
// 存储集合元素的载体数组
private Object[] container = new Object[CAPACITY];
// 存储当前有几个元素
private int count = 0;
@Override
public Iterator iterator() {
// 返回一个新的iterator
return new Iterator() {
// 记录当前迭代到的元素index
private int index = 0;
@Override
public boolean hasNext() {
// 如果index小于count,说明没有迭代完,还有下一个元素
return index < count;
}
@Override
public T next() {
// 返回元素,然后index自增
return (T) container[index++];
}
};
}
// 新增元素的方法
public void add(T obj) {
if (count >= CAPACITY) throw new RuntimeException("List is full");
container[count++] = obj;
}
}

编写好我们自己的MyList类之后,我们可以使用foreach方式迭代它,代码如下:

public static void main(String[] args) {
MyList stringMyList = new MyList<>();
stringMyList.add("hello");
stringMyList.add("world");
stringMyList.add("haha");
for (String s : stringMyList) {
System.out.println(s);
}
}
AutoCloseable

Java中有一些类,在使用完毕后必须close掉,以释放资源占用(例如IO类)。通常来说我们会这么使用它们。

public static void main(String[] args) {
FileReader reader = null;
try {
reader = new FileReader("~/demo.txt");
// 其他逻辑
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
try {
if (reader != null) reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

这里的FileReader在使用完毕之后,务必要记得在finally代码块中关闭。写起来十分复杂。

Java 7中新增了try with代码块语法。在代码执行完try块的时候,会自动调用资源的close方法,可以大量减少不必要的样板代码编写。

// 创建资源的语句写在try后的小括号内
try (FileReader reader = new FileReader("~/demo.txt")) {
reader.read();
// ...
} catch (IOException e) {
e.printStackTrace();
}
// 代码在执行到try代码块之后,会自动调用`close`方法
为了更清楚的向大家演示AutoCloseable的作用,我们自定义一个MyReader,代码如下所示:
class MyReader implements AutoCloseable {
public void doSomething() {
System.out.println("Do something");
}
@Override
public void close() {
// close是AutoClose中唯一一个方法,用来承载资源关闭时候执行的逻辑
System.out.println("I will close");
}
}

同样,我们使用try-with代码块调用自己的MyReader:

public static void main(String[] args) {
try (MyReader reader = new MyReader()) {
reader.doSomething();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Do something else");
}

程序的输出如下:

Do something

I will close

Do something else

写在最后

本博客会持续更新日常用到的Java特殊接口。