1. PriorityQueue中插入对象
优先级队列在插入元素时有个要求:插入的元素不能是null或者元素之间必须要能够
进行比较
class Card {
public int rank; // 数值
public String suit; // 花色
public Card(int rank, String suit) {
this.rank = rank;
this.suit = suit;
}
}
public class TestPriorityQueue {
public static void TestPriorityQueue()
{
PriorityQueue<Card> p = new PriorityQueue<>();
p.offer(new Card(1, "♠"));
p.offer(new Card(2, "♠"));
}
public static void main(String[] args) {
TestPriorityQueue();
}
}

这个 ClassCastException 报错的意思是:
Card类不能被强制转换为Comparable接口
PriorityQueue(优先队列 / 堆)在插入元素时,需要知道如何比较两个元素的大小,才能维护堆的结构。
- 如果往
PriorityQueue里放自定义对象 (如Card),Java 默认会尝试把它强转为Comparable接口,调用compareTo()方法来排序。 - 但是
Card类没有实现Comparable接口 ,也没有给PriorityQueue传入自定义比较器Comparator,所以就抛出了这个类型转换异常。
2. 元素的比较
2.1 基本类型的比较
在Java中,基本类型的对象可以直接比较大小。
public class test {
public static void main(String[] args) {
int a = 10;
int b = 20;
System.out.println(a > b); // flase
System.out.println(a < b); // true
System.out.println(a == b); // flase
System.out.println("============================");
char c1 = 'A';
char c2 = 'B';
System.out.println(c1 > c2); // flase
System.out.println(c1 < c2); // true
System.out.println(c1 == c2); // flase
System.out.println("============================");
boolean b1 = true;
boolean b2 = false;
System.out.println(b1 == b2); // flase
System.out.println(b1 != b2); // true
}
}
2.2 对象比较的问题
class Card {
public int rank; // 数值
public String suit; // 花色
public Card(int rank, String suit) {
this.rank = rank;
this.suit = suit;
}
}
public class test {
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); // 编译成功 ----> 打印false,因为c1和c2指向的是不同对象
System.out.println(c1 < c2); // 编译报错
System.out.println(c1 == c3); // 编译成功 ----> 打印true,因为c1和c3指向的是同一个对象
}
}
解释
1.为什么这里会报错?System.out.println(c1 > c2);
①Java 里不允许直接用 >、< 比较自定义对象 (比如写的 Card 类),只有基本数据类型 (int/double/char 等)才能用这些运算符比较大小。
②Card 是引用类型,Java 不知道你想比较什么,所以直接编译报错。
2.== 为什么不报错?
== 比较的是引用地址(两个变量是否指向同一个对象),不是对象内容,所以对所有引用类型都可用:
c1 == c2:false,因为它们是两个不同的new出来的对象c1 == c3:true,因为c3 = c1,指向同一个对象
3. 对象的比较
3.1 覆写equals
class Card {
public int rank; // 数值
public String suit; // 花色
public Card(int rank, String suit) {
this.rank = rank;
this.suit = suit;
}
public boolean equals(Object o) {
// 1. 自己和自己比较(同一个对象,直接返回true)
if (this == o) {
return true;
}
// 2. 如果o是null,或者不是Card类/子类,直接返回false
if (o == null || !(o instanceof Card)) {
return false;
}
// 3. 把o强转为Card类型,才能访问Card的成员变量
Card c = (Card) o;
// 4. 比较核心属性:
// - rank是int,直接用==比较
// - suit是String,引用类型,必须用equals()比较内容
return rank == c.rank && suit.equals(c.suit);
}
}
public class test {
public static void main(String[] args) {
Card c1 = new Card(1, "♠");
Card c2 = new Card(1, "♠");
System.out.println(c1 == c2); // false(不同对象)
System.out.println(c1.equals(c2)); // true(内容相同)
}
}
💡 补充:为什么要重写 equals()?
- 默认
equals()(来自Object类)和==效果一样,只比较引用地址 - 重写后,我们可以自定义:只要两个
Card的rank和suit都相同,就认为它们是 "相等" 的 - :equal只能按照相等进行比较,不能按照大于、小于的方式进行比较。
3.2 基于Comparble接口类的比较
Comparble是JDK提供的泛型的比较接口类。
public interface Comparable<E> {
// 返回值:
// < 0: 表示 this 指向的对象小于 o 指向的对象
// == 0: 表示 this 指向的对象等于 o 指向的对象
// > 0: 表示 this 指向的对象大于 o 指向的对象
int compareTo(E o);
}
如果要想按照大小与方式进行比较时:在定义类时,实现Comparble接口即可,然后在类
中重写compareTo方法。
class Card implements Comparable<Card> {
public int rank; // 数值
public String suit; // 花色
public Card(int rank, String suit) {
this.rank = rank;
this.suit = suit;
}
// 根据数值比较,不管花色
// 这里我们认为 null 是最小的
@Override
public int compareTo(Card o) {
if (o == null) {
return 1;
}
return rank - o.rank;
}
}
public class test {
public static void main(String[] args) {
Card p = new Card(1, "♠");
Card q = new Card(2, "♠");
Card o = new Card(1, "♠");
System.out.println(p.compareTo(o)); // == 0,表示牌相等
System.out.println(p.compareTo(q)); // < 0,表示 p 比较小
System.out.println(q.compareTo(p)); // > 0,表示 q 比较大
}
}
Compareble是java.lang中的接口类,可以直接使用。
3.3 基于比较器比较
用户自定义比较器类,实现Comparator接口
public interface Comparator<T> {
// 返回值:
// < 0: 表示 o1 指向的对象小于 o2 指向的对象
// == 0: 表示 o1 指向的对象等于 o2 指向的对象
// > 0: 表示 o1 指向的对象等于 o2 指向的对象
int compare(T o1, T o2);
}
注意:区分Comparable和Comparator。
import java.util.Comparator;
//Comparator是java.util 包中的泛型接口类,使用时必须导入对应的包
class Card {
public int rank; // 数值
public String suit; // 花色
public Card(int rank, String suit) {
this.rank = rank;
this.suit = suit;
}
}
class CardComparator implements Comparator<Card> {
// 根据数值比较,不管花色
// 这里我们认为 null 是最小的
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;
}
public static void main(String[] args){
Card p = new Card(1, "♠");
Card q = new Card(2, "♠");
Card o = new Card(1, "♠");
// 定义比较器对象
CardComparator cmptor = new CardComparator();
// 使用比较器对象进行比较
System.out.println(cmptor.compare(p, o)); // == 0,表示牌相等
System.out.println(cmptor.compare(p, q)); // < 0,表示 p 比较小
System.out.println(cmptor.compare(q, p)); // > 0,表示 q 比较大
}
}
3.4 三种方式对比
| 覆写的方法 | 说明 |
|---|---|
| Object.equals | 因为所有类都是继承自 Object 的,所以直接覆写即可,不过只能比较相等与否 |
| Comparable.compareTo | 需要手动实现接口,侵入性比较强,但一旦实现,每次用该类都有顺序,属于内部顺序 |
| Comparator.compare | 需要实现一个比较器对象,对待比较类的侵入性弱,但对算法代码实现侵入性强 |
💡:小技巧
compareTo:自己 vs 别人compare:a vs b
4. 集合框架中PriorityQueue的比较方式
集合框架中的PriorityQueue底层使用堆结构,因此其内部的元素必须要能够比大小PriorityQueue采用了:Comparble和Comparator两种方式。
- Comparble是默认的内部比较方式,如果用户插入自定义类型对象时,该类对象必须要实现Comparble接口,并覆写compareTo方法
- 用户也可以选择使用比较器对象,如果用户插入自定义类型对象时,必须要提供一个比较器类,让该类实现Comparator接口并覆写compare方法。
5. 使用PriorityQueue创建大小堆,解决TOPK问题
import java.util.Arrays;
import java.util.Comparator;
import java.util.PriorityQueue;
//使用比较器创建小根堆
class LessIntComp implements Comparator<Integer> {
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
}
//使用比较器创建大根堆
class GreaterIntComp implements Comparator<Integer>{
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
}
public class test<E> {
//求最小的K个数,通过比较器创建大根堆
public static int[] smallestK(int[] array, int k) {
if(k <= 0) {
return new int[k];
}
GreaterIntComp greaterCmp = new GreaterIntComp();
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(greaterCmp);
//先将前K个元素,创建大根堆
for(int i = 0; i < k; i++) {
maxHeap.offer(array[i]);
}
//从第K+1个元素开始,每次和堆顶元素比较
for (int i = k; i < array.length; i++) {
int top = maxHeap.peek();
if(array[i] < top) {
maxHeap.poll();
maxHeap.offer(array[i]);
}
}
//取出前K个
int[] ret = new int[k];
for (int i = 0; i < k; i++) {
int val = maxHeap.poll();
ret[i] = val;
}
return ret;
}
public static void main(String[] args) {
int[] array = {4,1,9,2,8,0,7,3,6,5};
int[] ret = smallestK(array,3);
System.out.println(Arrays.toString(ret));
}
}
💡一句话记住 Top-K 万能规则
- 求最小 K 个 → 用大根堆
- 求最大 K 个 → 用小根堆