Java 的 Comparable 接口(即 java.lang.Comparable)定义了对象的比较能力 —— 实现该接口的类,其实例可以与同类型对象进行比较。例如,数字可按大小比较,字符串可按字母顺序比较。Java 中许多内置类(如 Integer、String)都实现了这一接口。开发者也可通过自定义实现,让自己的类具备可比较性。
一个类实现 Comparable 接口后,意味着其所有实例之间可以建立比较规则。这种特性使得 Java 内置的排序功能(如集合框架中的排序方法)能够直接对该类的对象集合进行排序,无需额外定义比较逻辑。
需要注意的是,Comparable 接口的设计初衷是用于同一类型对象之间的比较。通俗地说,它适用于 “苹果与苹果比”“橙子与橙子比”,而不适合跨类型比较(如苹果与橙子、字符串与数字、日期与车牌等)。
Comparable 接口位于 java.lang 包中。接口定义如下:
package java.lang;
import java.util.*;
public interface Comparable<T> {
public int compareTo(T o);
}如你所见,Comparable 接口只包含一个 compareTo() 方法。
Comparable 接口的 compareTo() 方法接收一个对象作为参数,并返回一个 int 值。返回的 int 值表明调用 compareTo() 方法的对象与参数对象相比是更大、相等还是更小:
一个正值(1或更大)表示调用 compareTo() 方法的对象大于参数对象。
零(0)值表示两个对象相等。
一个负值(-1或更小)表示调用 compareTo() 方法的对象小于参数对象。
注意:在实现 Comparable 接口时,有一个要求,即该实现必须遵循以下传递性比较特性:
如果 A 大于 B,且 B 大于 C,那么 A 也一定大于 C。
为了更清晰地展示 Java 中 Comparable 接口的工作原理,我们来看一个简单示例:Java 的 Integer 类实现了 Comparable 接口,因此可以直接调用其 compareTo() 方法进行比较。示例如下:
public class ComparableExample {
public static void main(String[] args) {
Integer valA = Integer.valueOf(45);
Integer valB = Integer.valueOf(99);
int comparisonA = valA.compareTo(valB);
int comparisonB = valB.compareTo(valA);
System.out.println(comparisonA);
System.out.println(comparisonB);
}
}运行结果:
-1 1
由于值 45 小于 99,第一次比较 valA.compareTo(valB) 即(45.compareTo(99)) 的结果为 -1。在第二次比较中,当 99 与 45 比较时 valB.compareTo(valA) 即( 99.compareTo(45))的结果为 1,因为 99 大于 45。
若有需要,你也可以自行实现 Comparable 接口。下面以 Spaceship 类为例,展示如何让自定义类的实例之间具备可比较性:
package com.hxstrive.java_collection.comparable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class ComparableDemo {
public static void main(String[] args) {
List<Spaceship> list = new ArrayList<>();
list.add(new Spaceship("A", "A001"));
list.add(new Spaceship("B", "B099"));
list.add(new Spaceship("B", "B010"));
System.out.println("排序前:" + list);
Collections.sort(list);
System.out.println("排序后:" + list);
}
static class Spaceship implements Comparable {
// 飞船的级别/类型
private String spaceshipClass = null;
// 飞船的注册号
private String registrationNo = null;
public Spaceship(String spaceshipClass, String registrationNo) {
this.spaceshipClass = spaceshipClass;
this.registrationNo = registrationNo;
}
@Override
public int compareTo(Object o) {
// 强制类型转换
Spaceship other = (Spaceship) o;
// 先按飞船级别(spaceshipClass)进行比较,利用String的compareTo方法实现字符串自然顺序比较
int spaceshipClassComparison =
this.spaceshipClass.compareTo(other.spaceshipClass);
// 如果级别不同,直接返回级别比较结果
if(spaceshipClassComparison != 0) {
return spaceshipClassComparison;
}
// 如果级别相同,则按注册号(registrationNo)进行比较
return this.registrationNo.compareTo(other.registrationNo);
}
@Override
public String toString() {
return "Spaceship[" + spaceshipClass + ", " + registrationNo + "]";
}
}
}运行结果:
排序前:[Spaceship[A, A001], Spaceship[B, B099], Spaceship[B, B010]] 排序后:[Spaceship[A, A001], Spaceship[B, B010], Spaceship[B, B099]]
上述示例中,Spaceship 类的 compareTo() 方法中,通过比较 spaceshipClass 和 registrationNo 实现多个维度进行排序。如果对象的 spaceshipClass 属性相同,就继续比较 registrationNo 属性。
如果你仔细观察就会发现,在 compareTo() 方法中,如下代码采用了强制类型转换:
Spaceship other = (Spaceship) o; // 可能抛出 ClassCastException 异常
这里采用强制类型转换是因为在实现 Comparable 接口时没有指定类型参数,导致 compareTo() 方法的参数类型为 Object。
注意,如果 compareTo() 方法输入参数与调用 compareTo() 方法的对象所属的类不同,compareTo() 方法应该抛出 ClassCastException 异常。因此,不需要进行显式的类型检查。你只需将其强制转换为所需的类。如果类不匹配,Java 虚拟机将抛出 ClassCastException 异常。
为了避免上述问题,我们可以通过泛型技术,避免进行强制类型转换,以及 ClassCastException 异常。只需在实现 Comparable 时在后面添加泛型类型,如 Comparable<类型>,例如:
package com.hxstrive.java_collection.comparable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class ComparableDemo2 {
public static void main(String[] args) {
List<Spaceship> list = new ArrayList<>();
list.add(new Spaceship("A", "A001"));
list.add(new Spaceship("B", "B099"));
list.add(new Spaceship("B", "B010"));
System.out.println("排序前:" + list);
Collections.sort(list);
System.out.println("排序后:" + list);
}
static class Spaceship implements Comparable<Spaceship> {
// 飞船的级别/类型
private String spaceshipClass = null;
// 飞船的注册号
private String registrationNo = null;
public Spaceship(String spaceshipClass, String registrationNo) {
this.spaceshipClass = spaceshipClass;
this.registrationNo = registrationNo;
}
@Override
public int compareTo(Spaceship o) {
// 先按飞船级别(spaceshipClass)进行比较,利用String的compareTo方法实现字符串自然顺序比较
int spaceshipClassComparison =
this.spaceshipClass.compareTo(o.spaceshipClass);
// 如果级别不同,直接返回级别比较结果
if(spaceshipClassComparison != 0) {
return spaceshipClassComparison;
}
// 如果级别相同,则按注册号(registrationNo)进行比较
return this.registrationNo.compareTo(o.registrationNo);
}
@Override
public String toString() {
return "Spaceship[" + spaceshipClass + ", " + registrationNo + "]";
}
}
}运行结果:
排序前:[Spaceship[A, A001], Spaceship[B, B099], Spaceship[B, B010]] 排序后:[Spaceship[A, A001], Spaceship[B, B010], Spaceship[B, B099]]
注意,如果 compareTo() 方法的参数对象为 null,compareTo() 方法应该抛出 NullPointerException 异常。这意味着你在比较对象之前不必进行空值检查。
更多内容请参考https://docs.oracle.com/javase/8/docs/api/java/lang/Comparable.html API 文档。