文章目录
- 一、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类,而Object的equals方法底层就是用==实现的,本质是比较引用地址:
java
// Object类的equals方法源码
public boolean equals(Object obj) {
return (this == obj); // 直接比较引用地址
}
但实际开发中,我们往往需要比较"对象内容",此时就需要手动实现比较逻辑。
二、Java对象的三种比较方式
2.1 方式一:覆写Object的equals方法(仅比较"相等性")
equals方法的作用是判断两个对象"内容是否相等"
覆写套路:
- 先判断自比较:若
this == o,直接返回true(同一对象内容必然相同); - 若
o == null或o不是当前类的实例,返回false; - 强转类型:将
o强转为当前类,再比较关键属性(Card的rank和suit); - 引用类型属性需用
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接口(内部比较,侵入性强)
Comparable是Java.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接口(外部比较,灵活性高)
Comparator是Java.util包下的泛型接口,属于外部比较器------无需修改目标类的源码,只需单独实现一个比较器类,定义比较规则。
compare返回值: 与Comparable一致:
- 返回值
< 0:o1小于o2; - 返回值
== 0:o1等于o2; - 返回值
> 0:o1大于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♥
}
}
}