Java 集合:Comparable 接口

Java 的 Comparable 接口(即 java.lang.Comparable)定义了对象的比较能力 —— 实现该接口的类,其实例可以与同类型对象进行比较。例如,数字可按大小比较,字符串可按字母顺序比较。Java 中许多内置类(如 Integer、String)都实现了这一接口。开发者也可通过自定义实现,让自己的类具备可比较性。

一个类实现 Comparable 接口后,意味着其所有实例之间可以建立比较规则。这种特性使得 Java 内置的排序功能(如集合框架中的排序方法)能够直接对该类的对象集合进行排序,无需额外定义比较逻辑。

需要注意的是,Comparable 接口的设计初衷是用于同一类型对象之间的比较。通俗地说,它适用于 “苹果与苹果比”“橙子与橙子比”,而不适合跨类型比较(如苹果与橙子、字符串与数字、日期与车牌等)。

Comparable 的定义

Comparable 接口位于 java.lang 包中。接口定义如下:

package java.lang;

import java.util.*;

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

如你所见,Comparable 接口只包含一个 compareTo() 方法。

compareTo() 方法

Comparable 接口的 compareTo() 方法接收一个对象作为参数,并返回一个 int 值。返回的 int 值表明调用 compareTo() 方法的对象与参数对象相比是更大、相等还是更小:

  • 一个正值(1或更大)表示调用 compareTo() 方法的对象大于参数对象。

  • 零(0)值表示两个对象相等。

  • 一个负值(-1或更小)表示调用 compareTo() 方法的对象小于参数对象。

注意:在实现 Comparable 接口时,有一个要求,即该实现必须遵循以下传递性比较特性:

如果 A 大于 B,且 B 大于 C,那么 A 也一定大于 C。

Comparable 示例

为了更清晰地展示 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 接口

若有需要,你也可以自行实现 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 文档。

  

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
其他应用
公众号