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 文档。