本系列可作为JAVA学习系列的笔记,文中提到的一些练习的代码,小编会将代码复制下来,大家复制下来就可以练习了,方便大家学习。
点赞关注不迷路!您的点赞、关注和收藏是对小编最大的支持和鼓励!
系列文章目录
JAVA数据结构 DAY9 equals、Comparable、Comparator 与 PriorityQueue 深度解析
拓展目录
手把手教你用 ArrayList 实现杨辉三角:从逻辑推导到每行代码详解
Java 中的 hashCode () 与 equals () 核心原理、契约规范、重写实践与面试全解
目录
目录
1.3 自定义对象放入 PriorityQueue 异常演示
前言
小编作为新晋码农一枚,会定期整理一些写的比较好的代码,作为自己的学习笔记,会试着做一下批注和补充,如转载或者参考他人文献会标明出处,非商用,如有侵权会删改!欢迎大家斧正和讨论!
在 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 关键结论
==对引用类型:默认比较对象内存地址,而非内容;>/<:Java 未为引用类型定义大小比较规则,直接使用编译报错;- 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 特点
- ✅ 类自带排序规则,一次实现处处使用;
- ✅ 直接支持
PriorityQueue、Collections.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 使用总结
- 插入自定义对象,必须满足:
- 类实现
Comparable接口; - 或传入
Comparator比较器对象;
- 类实现
- 默认是小根堆,可通过比较器改为大根堆。
五、实战:大小堆实现与 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 的大根堆,遍历数组:
- 堆未满:直接插入;
- 堆已满:当前元素 < 堆顶 → 弹出堆顶,插入当前元素;
- 最终堆中元素即为最小 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]
}
}
六、全文总结
- 对象比较核心问题:引用类型无法直接用
>/<比较,==仅比较地址; - 三种比较方案:
equals():仅判等,无大小比较;Comparable:内部默认排序,侵入性强,单规则;Comparator:外部灵活排序,无侵入,多规则;
- PriorityQueue:底层依赖堆,必须用
Comparable或Comparator比较; - 实战场景:通过
Comparator实现大小堆,解决 TOP-K 高频面试题。
掌握以上内容,Java 对象比较、优先级队列使用、排序场景开发将完全无压力!

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

259

被折叠的 条评论
为什么被折叠?



