JAVA数据结构 DAY9 equals、Comparable、Comparator 与 PriorityQueue 深度解析

本系列可作为JAVA学习系列的笔记,文中提到的一些练习的代码,小编会将代码复制下来,大家复制下来就可以练习了,方便大家学习。

点赞关注不迷路!您的点赞、关注和收藏是对小编最大的支持和鼓励! 

系列文章目录

JAVA初阶---------已更完

JAVA数据结构 DAY1-集合和时空复杂度

JAVA数据结构 DAY2-包装类和泛型

JAVA数据结构 DAY3-List接口

JAVA数据结构 DAY4-ArrayList

JAVA数据结构 DAY5-LinkedList

JAVA数据结构 DAY6-栈和队列

JAVA数据结构 DAY7-二叉树

JAVA数据结构 DAY8-堆

JAVA数据结构 DAY9 equals、Comparable、Comparator 与 PriorityQueue 深度解析

JAVA数据结构 DAY10-排序

JAVA数据结构 DAY11-Set和Map


拓展目录

手把手教你用 ArrayList 实现杨辉三角:从逻辑推导到每行代码详解

链表高频 6 题精讲 | 从入门到熟练掌握链表操作

二叉树高频题精讲 | 从入门到熟练掌握二叉树操作

二叉树高频题精讲 | 从入门到熟练掌握二叉树操作2

二叉树高频题精讲 | 从入门到熟练掌握二叉树操作3

Java 中的 hashCode () 与 equals () 核心原理、契约规范、重写实践与面试全解

Set&Map高频题精讲 | 从入门到熟练掌握


目录

目录

系列文章目录

拓展目录

目录

前言

一、Java 对象比较的核心问题

1.1 基本类型 vs 引用类型比较差异

1.2 关键结论

1.3 自定义对象放入 PriorityQueue 异常演示

二、Java 对象比较的三种标准方案

方案 1:覆写 Object 类 equals () 方法

1.1 覆写 equals () 标准模板

1.2 特点

方案 2:实现 Comparable 接口(内部比较器)

2.1 接口源码

2.2 实现示例

2.3 测试使用

2.4 特点

方案 3:实现 Comparator 接口(外部比较器)

3.1 接口源码

3.2 实现示例

3.3 测试使用

3.4 特点

三、三种比较方式详细对比

四、PriorityQueue 底层比较原理(源码级)

4.1 核心构造方法

4.2 上浮比较逻辑

4.3 PriorityQueue 使用总结

五、实战:大小堆实现与 TOP-K 问题

5.1 用 Comparator 实现大根堆 / 小根堆

5.2 TOP-K 经典问题:求数组最小 K 个数

六、全文总结

总结


前言

小编作为新晋码农一枚,会定期整理一些写的比较好的代码,作为自己的学习笔记,会试着做一下批注和补充,如转载或者参考他人文献会标明出处,非商用,如有侵权会删改!欢迎大家斧正和讨论!

在 Java 编程中,基本数据类型可以直接使用 ><== 比较大小,但自定义对象无法直接进行大小比较,若直接将对象放入 PriorityQueue 优先级队列,还会抛出 ClassCastException 类型转换异常。

本文将从对象比较的问题根源三种比较方案详解PriorityQueue 底层比较原理大小堆与 TOP-K 实战四个维度,带你彻底吃透 Java 对象比较的核心知识。


一、Java 对象比较的核心问题

1.1 基本类型 vs 引用类型比较差异

Java 中基本类型(int、char、boolean 等)和引用类型(自定义对象、包装类)的比较规则完全不同:

public class TestCompare {
    public static void main(String[] args) {
        // 1. 基本类型:直接支持 > < == 比较
        int a = 10, b = 20;
        System.out.println(a > b);   // false
        System.out.println(a < b);   // true
        System.out.println(a == b);  // false

        char c1 = 'A', c2 = 'B';
        System.out.println(c1 < c2); // true

        boolean b1 = true, b2 = false;
        System.out.println(b1 != b2); // true
    }
}

引用类型(自定义对象)

class Card {
    public int rank;   // 牌面数值
    public String suit;// 花色
    public Card(int rank, String suit) {
        this.rank = rank;
        this.suit = suit;
    }
}

public class TestObjectCompare {
    public static void main(String[] args) {
        Card c1 = new Card(1, "♠");
        Card c2 = new Card(2, "♠");
        Card c3 = c1;

        // 编译报错:引用类型不支持 > < 比较
        // System.out.println(c1 > c2);
        // System.out.println(c1 < c2);

        // 编译通过:== 比较的是对象内存地址
        System.out.println(c1 == c2); // false(不同对象)
        System.out.println(c1 == c3); // true(同一对象)
    }
}

1.2 关键结论

  1. == 对引用类型:默认比较对象内存地址,而非内容;
  2. > / <:Java 未为引用类型定义大小比较规则,直接使用编译报错
  3. PriorityQueue 限制:底层是堆结构,插入元素必须支持大小比较,否则抛异常。

1.3 自定义对象放入 PriorityQueue 异常演示

import java.util.PriorityQueue;

public class TestPriorityQueueError {
    public static void main(String[] args) {
        PriorityQueue<Card> pq = new PriorityQueue<>();
        pq.offer(new Card(1, "♠"));
        pq.offer(new Card(2, "♠"));
    }
}

运行异常

java.lang.ClassCastException: Card cannot be cast to java.lang.Comparable

异常原因PriorityQueue 插入元素时需要比较大小调整堆结构,而 Card 对象没有实现比较规则。


二、Java 对象比较的三种标准方案

Java 为对象比较提供了三种官方方案,分别适用于判等默认排序灵活排序场景,下面逐一详解。

方案 1:覆写 Object 类 equals () 方法

Object 是所有类的父类,其默认 equals() 等价于 ==,只比较地址。若需要比较对象内容是否相等,必须手动覆写。

1.1 覆写 equals () 标准模板
import java.util.Objects;

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. 同一对象:直接返回 true
        if (this == o) return true;
        // 2. 空值 或 类型不匹配:返回 false
        if (o == null || getClass() != o.getClass()) return false;
        // 3. 强制类型转换
        Card card = (Card) o;
        // 4. 比较核心成员变量(引用类型用 Objects.equals() 避免空指针)
        return rank == card.rank && Objects.equals(suit, card.suit);
    }
}
1.2 特点
  • ✅ 仅用于判断对象是否相等,无大小比较能力;
  • ✅ 适用于判重、判等场景(如集合去重);
  • 无法用于 PriorityQueue(不支持大小比较)。

方案 2:实现 Comparable 接口(内部比较器)

java.lang.Comparable内部比较器,为类定义默认的自然排序规则,重写 compareTo() 方法即可实现大小比较。

2.1 接口源码
public interface Comparable<E> {
    // 返回值规则:
    // < 0:当前对象 < 目标对象
    // = 0:当前对象 = 目标对象
    // > 0:当前对象 > 目标对象
    int compareTo(E o);
}
2.2 实现示例
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 int compareTo(Card o) {
        // 空对象处理:认为 null 最小
        if (o == null) return 1;
        // 按牌面数值升序排序
        return this.rank - o.rank;
    }
}
2.3 测试使用
public class TestComparable {
    public static void main(String[] args) {
        Card c1 = new Card(1, "♠");
        Card c2 = new Card(2, "♠");
        Card c3 = new Card(1, "♥");

        System.out.println(c1.compareTo(c3)); // 0(数值相等)
        System.out.println(c1.compareTo(c2)); // -1(c1 < c2)
        System.out.println(c2.compareTo(c1)); // 1(c2 > c1)

        // 可直接放入 PriorityQueue
        PriorityQueue<Card> pq = new PriorityQueue<>();
        pq.offer(c1);
        pq.offer(c2);
    }
}
2.4 特点
  • 类自带排序规则,一次实现处处使用;
  • ✅ 直接支持 PriorityQueueCollections.sort() 排序;
  • 侵入性强,修改原类;
  • 仅支持一种排序规则(无法同时按数值、花色排序)。

方案 3:实现 Comparator 接口(外部比较器)

java.util.Comparator外部比较器不修改原类,独立定义排序规则,支持多种排序逻辑灵活切换。

3.1 接口源码
public interface Comparator<T> {
    // 返回值规则:
    // < 0:o1 < o2
    // = 0:o1 = o2
    // > 0:o1 > o2
    int compare(T o1, T o2);
}
3.2 实现示例
import java.util.Comparator;

// 比较器1:按牌面数值升序
class CardRankComparator implements Comparator<Card> {
    @Override
    public int compare(Card o1, Card o2) {
        if (o1 == o2) return 0;
        if (o1 == null) return -1;
        if (o2 == null) return 1;
        return o1.rank - o2.rank;
    }
}

// 比较器2:按花色排序(扩展规则)
class CardSuitComparator implements Comparator<Card> {
    @Override
    public int compare(Card o1, Card o2) {
        return o1.suit.compareTo(o2.suit);
    }
}
3.3 测试使用
public class TestComparator {
    public static void main(String[] args) {
        Card c1 = new Card(1, "♠");
        Card c2 = new Card(2, "♠");

        // 创建比较器对象
        CardRankComparator comparator = new CardRankComparator();
        System.out.println(comparator.compare(c1, c2)); // -1

        // PriorityQueue 传入比较器
        PriorityQueue<Card> pq = new PriorityQueue<>(new CardRankComparator());
        pq.offer(c1);
        pq.offer(c2);
    }
}
3.4 特点
  • 无侵入性,不修改原类;
  • 支持多套排序规则,随时切换;
  • ✅ 适用于无法修改源码的类(如 JDK 内置类);
  • ❌ 需要额外创建比较器对象。

三、三种比较方式详细对比

比较方式核心方法作用侵入性排序规则数量适用场景
Object.equals()equals()仅判断是否相等1 种对象判重、判等
Comparable(内部)compareTo()大小比较 + 自然排序1 种类默认排序
Comparator(外部)compare()大小比较 + 灵活排序多种多规则排序、无法修改原类

四、PriorityQueue 底层比较原理(源码级)

JDK 中 PriorityQueue 底层是二叉堆同时支持 Comparable 和 Comparator 两种比较方式,核心逻辑在上浮(siftUp) 方法中:

4.1 核心构造方法

public class PriorityQueue<E> extends AbstractQueue<E> {
    // 存储比较器对象
    private final Comparator<? super E> comparator;

    // 无参构造:默认使用 Comparable 比较
    public PriorityQueue() {
        this(DEFAULT_INITIAL_CAPACITY, null);
    }

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

4.2 上浮比较逻辑

// 核心上浮方法
private void siftUp(int k, E x) {
    // 有比较器:用 Comparator 比较
    if (comparator != null)
        siftUpUsingComparator(k, x);
    // 无比较器:用 Comparable 比较
    else
        siftUpComparable(k, x);
}

// 使用 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];
        // 调用 compareTo() 比较
        if (key.compareTo((E) e) >= 0)
            break;
        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];
        // 调用 compare() 比较
        if (comparator.compare(x, (E) e) >= 0)
            break;
        queue[k] = e;
        k = parent;
    }
    queue[k] = x;
}

4.3 PriorityQueue 使用总结

  1. 插入自定义对象,必须满足:
    • 类实现 Comparable 接口;
    • 或传入 Comparator 比较器对象;
  2. 默认是小根堆,可通过比较器改为大根堆

五、实战:大小堆实现与 TOP-K 问题

5.1 用 Comparator 实现大根堆 / 小根堆

PriorityQueue 默认是小根堆,通过比较器反转比较规则即可实现大根堆

// 小根堆比较器(默认)
class SmallHeapComparator implements Comparator<Integer> {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o1 - o2;
    }
}

// 大根堆比较器
class BigHeapComparator implements Comparator<Integer> {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o2 - o1;
    }
}

5.2 TOP-K 经典问题:求数组最小 K 个数

思路:维护一个大小为 K 的大根堆,遍历数组:

  1. 堆未满:直接插入;
  2. 堆已满:当前元素 < 堆顶 → 弹出堆顶,插入当前元素;
  3. 最终堆中元素即为最小 K 个数
import java.util.PriorityQueue;
import java.util.Arrays;

public class TopKTest {
    public static int[] smallestK(int[] array, int k) {
        // 边界判断
        if (k <= 0 || array == null || array.length == 0) {
            return new int[0];
        }

        // 创建大根堆
        PriorityQueue<Integer> maxHeap = new PriorityQueue<>(new BigHeapComparator());

        // 遍历数组维护堆
        for (int num : array) {
            if (maxHeap.size() < k) {
                maxHeap.offer(num);
            } else {
                if (num < maxHeap.peek()) {
                    maxHeap.poll();
                    maxHeap.offer(num);
                }
            }
        }

        // 取出结果
        int[] res = new int[k];
        for (int i = 0; i < k; i++) {
            res[i] = maxHeap.poll();
        }
        return res;
    }

    public static void main(String[] args) {
        int[] array = {4,1,9,2,8,0,7,3,6,5};
        int[] res = smallestK(array, 3);
        System.out.println(Arrays.toString(res)); // [0,1,2]
    }
}

六、全文总结

  1. 对象比较核心问题:引用类型无法直接用 >/< 比较,== 仅比较地址;
  2. 三种比较方案
    • equals():仅判等,无大小比较;
    • Comparable:内部默认排序,侵入性强,单规则;
    • Comparator:外部灵活排序,无侵入,多规则;
  3. PriorityQueue:底层依赖堆,必须用 ComparableComparator 比较;
  4. 实战场景:通过 Comparator 实现大小堆,解决 TOP-K 高频面试题。

掌握以上内容,Java 对象比较、优先级队列使用、排序场景开发将完全无压力!


总结

以上就是今天要讲的内容,本文简单记录了java数据结构,仅作为一份简单的笔记使用,大家根据注释理解,您的点赞关注收藏就是对小编最大的鼓励!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Yvonne爱编码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值