【数据结构】Java对象的比较

文章目录

  • 一、Java对象比较的痛点:为什么不能直接用>、<比较?
    • [1.1 基本类型:直接比较,简单直接](#1.1 基本类型:直接比较,简单直接)
    • [1.2 引用类型](#1.2 引用类型)
  • 二、Java对象的三种比较方式
    • [2.1 方式一:覆写Object的equals方法(仅比较"相等性")](#2.1 方式一:覆写Object的equals方法(仅比较“相等性”))
    • [2.2 方式二:实现Comparable接口(内部比较,侵入性强)](#2.2 方式二:实现Comparable接口(内部比较,侵入性强))
    • [2.3 方式三:实现Comparator接口(外部比较,灵活性高)](#2.3 方式三:实现Comparator接口(外部比较,灵活性高))
    • 三种比较方式对比
  • 三、PriorityQueue中的对象比较:Comparable与Comparator
    • [3.1 底层实现原理](#3.1 底层实现原理)
    • [3.2 用Comparable实现PriorityQueue](#3.2 用Comparable实现PriorityQueue)
    • [3.3 用Comparator实现PriorityQueue](#3.3 用Comparator实现PriorityQueue)

一、Java对象比较的痛点:为什么不能直接用>、<比较?

在Java中,数据类型分为基本类型 (int、char、boolean等)和引用类型(自定义类、String等)

1.1 基本类型:直接比较,简单直接

基本类型的比较无需额外操作,可直接使用>、<、==、!=等运算符,因为JVM已为其定义了明确的数值大小规则。

java 复制代码
    public static void main(String[] args) {
        int a = 10, b = 20;
        System.out.println(a < b); // true(数值直接比较)
        char c1 = 'A', c2 = 'B';
        System.out.println(c1 > c2); // false(ASCII码值比较)
        boolean b1 = true, b2 = false;
        System.out.println(b1 == b2); // false(布尔值直接比较)
    }

1.2 引用类型

对于自定义引用类型,直接使用>、<编译报错 ,而==的行为也并非比较对象内容,而是比较引用地址。

java 复制代码
    public static void main(String[] args) {
        Student s1 = new Student("daisy", 18);
        Student s2 = new Student("rare", 20);
        Student s3 = s1; // s3指向s1的地址
        
        // System.out.println(s1 > s2); // 编译报错:引用类型不能用>比较
        System.out.println(c1 == c2); // false(s1和s2指向不同对象,地址不同)
        System.out.println(c1 == c3); // true(s1和s3指向同一对象,地址相同)
    }

所有自定义类默认继承自Object类,而Objectequals方法底层就是用==实现的,本质是比较引用地址:

java 复制代码
// Object类的equals方法源码
public boolean equals(Object obj) {
    return (this == obj); // 直接比较引用地址
}

但实际开发中,我们往往需要比较"对象内容",此时就需要手动实现比较逻辑。

二、Java对象的三种比较方式

2.1 方式一:覆写Object的equals方法(仅比较"相等性")

equals方法的作用是判断两个对象"内容是否相等"

覆写套路:

  1. 先判断自比较:若this == o,直接返回true(同一对象内容必然相同);
  2. o == nullo不是当前类的实例,返回false
  3. 强转类型:将o强转为当前类,再比较关键属性(Cardranksuit);
  4. 引用类型属性需用equals比较:例如String,不能用==

示例代码:

java 复制代码
class Card {
    public int rank;
    public String suit;
    public Card(int rank, String suit) {
        this.rank = rank;
        this.suit = suit;
    }

    @Override
    public boolean equals(Object o) {
        // 1. 自比较
        if (this == o) return true;
        // 2. 排除null和类型不匹配
        if (o == null || !(o instanceof Card)) return false;
        // 3. 强转并比较关键属性
        Card card = (Card) o;
        // 4. 基本类型用==,引用类型用equals
        return rank == card.rank && suit.equals(card.suit);
    }
}

局限性: 仅能判断"相等与否",无法比较"大小关系"(如判断一张牌是否比另一张大),因此不适用于需要排序的场景(如PriorityQueue)。

2.2 方式二:实现Comparable接口(内部比较,侵入性强)

ComparableJava.lang包下的泛型接口,定义了对象的自然排序规则。通过实现该接口并覆写compareTo方法,可让类本身具备"大小比较能力",属于内部比较

compareTo返回值:

  • 返回值 < 0:当前对象(this)小于目标对象(o);
  • 返回值 == 0:当前对象等于目标对象;
  • 返回值 > 0:当前对象大于目标对象。

示例代码(Card按数值比较):

java 复制代码
// 实现Comparable接口,指定比较的类型为Card
class Card implements Comparable<Card> {
    public int rank;
    public String suit;
    public Card(int rank, String suit) {
        this.rank = rank;
        this.suit = suit;
    }

    // 覆写compareTo:按rank从小到大排序
    @Override
    public int compareTo(Card o) {
        if (o == null) return 1; // 约定null为最小
        return this.rank - o.rank; // 数值差即比较结果
    }
}

优缺点:

  • 优点:一旦实现,类的所有实例都自带排序能力,无需额外代码,适合固定排序规则的场景;
  • 缺点:侵入性强,类定义时必须实现接口,若需修改排序规则,需修改类源码,灵活性低。

2.3 方式三:实现Comparator接口(外部比较,灵活性高)

ComparatorJava.util包下的泛型接口,属于外部比较器------无需修改目标类的源码,只需单独实现一个比较器类,定义比较规则。

compare返回值:Comparable一致:

  • 返回值 < 0o1小于o2
  • 返回值 == 0o1等于o2
  • 返回值 > 0o1大于o2

示例代码(Card按数值比较的外部比较器):

java 复制代码
// 1. 定义比较器类,实现Comparator<Card>
class CardComparator implements Comparator<Card> {
    @Override
    public int compare(Card o1, Card o2) {
        if (o1 == o2) return 0;
        if (o1 == null) return -1; // 约定null为最小
        if (o2 == null) return 1;
        return o1.rank - o2.rank; // 按数值从小到大排序
    }
}

// 2. 目标类Card无需修改
class Card {
    public int rank;
    public String suit;
    public Card(int rank, String suit) {
        this.rank = rank;
        this.suit = suit;
    }
}

优缺点:

  • 优点:无侵入性(不修改目标类),可定义多个比较器(如"按数值排序""按花色排序"),灵活性极高,适合"动态排序规则"的场景;
  • 缺点:每次比较需创建比较器对象,若比较逻辑复杂,会增加少量代码量。

三种比较方式对比

比较方式 覆写/实现的方法 特点 适用场景
覆写Object.equals equals(Object o) 仅比较相等性,无侵入性 只需判断对象内容是否相同的场景
实现Comparable接口 compareTo(E o) 内部比较,侵入性强,固定排序规则 类的排序规则固定
实现Comparator接口 compare(T o1, T o2) 外部比较,无侵入性,灵活切换排序规则 需动态修改排序规则

三、PriorityQueue中的对象比较:Comparable与Comparator

PriorityQueue(优先级队列)是Java集合框架中典型的需要对象比较的场景。底层基于堆结构,插入元素时必须通过比较调整堆,以保证小根堆或大根堆的性质。

PriorityQueue支持两种比较方式,优先级:Comparator(外部) > Comparable(内部)

3.1 底层实现原理

PriorityQueue的源码中,通过comparator成员变量存储外部比较器,插入元素时通过offer方法触发堆的向上调整,并根据是否有外部比较器选择不同的比较逻辑:

java 复制代码
public class PriorityQueue<E> extends AbstractQueue<E> {
    private final Comparator<? super E> comparator; // 外部比较器
    private Object[] queue; // 存储堆元素的数组

    // 构造方法1:无外部比较器,使用Comparable(内部比较)
    public PriorityQueue() {
        this(11, null); // 默认容量11,comparator为null
    }

    // 构造方法2:传入外部比较器,使用Comparator(外部比较)
    public PriorityQueue(int initialCapacity, Comparator<? super E> comparator) {
        this.queue = new Object[initialCapacity];
        this.comparator = comparator;
    }

    // 插入元素:触发向上调整
    public boolean offer(E e) {
        // ... 省略空判断 ...
        siftUp(size++, e); // 向上调整堆
        return true;
    }

    // 向上调整:根据是否有外部比较器选择逻辑
    private void siftUp(int k, E x) {
        if (comparator != null) {
            siftUpUsingComparator(k, x); // 用外部比较器
        } else {
            siftUpComparable(k, x); // 用内部Comparable
        }
    }

    // 内部比较(Comparable)
    private void siftUpComparable(int k, E x) {
        Comparable<? super E> key = (Comparable<? super E>) x;
        while (k > 0) {
            int parent = (k - 1) >>> 1; // 父节点索引
            Object e = queue[parent];
            if (key.compareTo((E) e) >= 0) break; // 用compareTo比较
            queue[k] = e;
            k = parent;
        }
        queue[k] = key;
    }

    // 外部比较(Comparator)
    private void siftUpUsingComparator(int k, E x) {
        while (k > 0) {
            int parent = (k - 1) >>> 1;
            Object e = queue[parent];
            if (comparator.compare(x, (E) e) >= 0) break; // 用comparator比较
            queue[k] = e;
            k = parent;
        }
        queue[k] = x;
    }
}

3.2 用Comparable实现PriorityQueue

如果自定义类实现了Comparable接口,PriorityQueue会自动使用其compareTo方法作为排序规则(默认小根堆):

java 复制代码
// Card实现Comparable(按数值从小到大)
class Card implements Comparable<Card> {
    public int rank;
    public String suit;
    public Card(int rank, String suit) {
        this.rank = rank;
        this.suit = suit;
    }

    @Override
    public String toString() {
        return rank + suit; // 便于打印
    }

    @Override
    public int compareTo(Card o) {
        return this.rank - o.rank; // 小根堆:数值小的优先
    }
}

// 测试PriorityQueue
public class TestPriorityQueue {
    public static void main(String[] args) {
        PriorityQueue<Card> pq = new PriorityQueue<>();
        pq.offer(new Card(3, "♠"));
        pq.offer(new Card(1, "♥"));
        pq.offer(new Card(2, "♦"));

        // 输出:1♥ 3♠ 2♦(堆的物理存储,逻辑上是小根堆,弹出时按从小到大)
        System.out.println(pq);
        // 弹出元素(小根堆每次弹出最小元素)
        while (!pq.isEmpty()) {
            System.out.print(pq.poll() + " "); // 输出:1♥ 2♦ 3♠
        }
    }
}

3.3 用Comparator实现PriorityQueue

若需修改排序规则(大根堆),无需修改Card类,只需传入自定义的Comparator:

java 复制代码
import java.util.Comparator;
import java.util.PriorityQueue;

// 自定义大根堆比较器:按数值从大到小
class CardMaxComparator implements Comparator<Card> {
    @Override
    public int compare(Card o1, Card o2) {
        return o2.rank - o1.rank; // 大根堆:o2.rank大则返回正,o1排在后面
    }
}

class Card {
    public int rank;
    public String suit;
    public Card(int rank, String suit) {
        this.rank = rank;
        this.suit = suit;
    }

    @Override
    public String toString() {
        return rank + suit;
    }
}

// 测试大根堆
public class TestPriorityQueueMax {
    public static void main(String[] args) {
        // 传入外部比较器,创建大根堆
        PriorityQueue<Card> maxHeap = new PriorityQueue<>(new CardMaxComparator());
        maxHeap.offer(new Card(3, "♠"));
        maxHeap.offer(new Card(1, "♥"));
        maxHeap.offer(new Card(2, "♦"));

        // 弹出元素(大根堆每次弹出最大元素)
        while (!maxHeap.isEmpty()) {
            System.out.print(maxHeap.poll() + " "); // 输出:3♠ 2♦ 1♥
        }
    }
}
相关推荐
橘子132 小时前
Linux线程——一些概念(七)
java·redis·缓存
magic_kid_20102 小时前
IDEA 复制到 Windows 远程桌面失败的原因与解决方案
java·ide·intellij-idea
风月歌2 小时前
基于微信小程序的学习资料销售平台源代码(源码+文档+数据库)
java·数据库·mysql·微信小程序·小程序·毕业设计·源码
历程里程碑2 小时前
C++ 16:C++11新特化
c语言·开发语言·数据结构·c++·经验分享
_dindong2 小时前
算法杂谈:回溯路线
数据结构·算法·动态规划·bfs·宽度优先
巴拉巴拉~~2 小时前
KMP 算法通用步进器组件:KmpStepperWidget 横向 / 纵向 + 匹配进度 + 全样式自定义
java·服务器·开发语言
贺今宵2 小时前
使用idea启动一个springboot项目
java·ide·intellij-idea
AAA简单玩转程序设计2 小时前
Java传参还在瞎传?这3个进阶基础技巧少走1年弯路
java
伍一512 小时前
芋道框架下的进销存升级(三):Yudao-ERP2异步导出/导入Excel的设计与实现
java·excel·异步导出excel