上节课我们讲了优先级队列,优先级队列在插入元素时有个要求:插入的元素不能是null或者元素之间必须要能够进行比较,为了简单起见,我们只是插入了Integer类型,那优先级队列中能否插入自定义类型对象呢?
public class Card {
public int rank;
public String suit;
public Card(int rank, String suit) {
this.rank = rank;
this.suit = suit;
}
}
import java.util.PriorityQueue;
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();
}
}优先级队列底层使用堆,而向堆中插入元素时,为了满足堆的性质,必须要进行元素的比较,而此时Card是没有办法直接进行比较的,因此抛出异常。

在Java中,基本类型的对象可以直接比较大小。
public static void main(String[] args) {
int a = 10;
int b = 20;
System.out.println(a > b);
System.out.println(a < b);
System.out.println(a == b);
char c1 = 'A';
char c2 = 'B';
System.out.println(c1 > c2);
System.out.println(c1 < c2);
System.out.println(c1 == c2);
boolean b1 = true;
boolean b2 = false;
System.out.println(b1 == b2);
System.out.println(b1 != b2);
}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 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指向的是同一个对象
}
}c1、c2和c3分别是Card类型的引用变量,上述代码在比较编译时: c1 > c2 编译失败 c1== c2 编译成功 c1 < c2 编译失败
从编译结果可以看出,Java中引用类型的变量不能直接按照 > 或者 < 方式进行比较。 那为什么可以比较? 因为:对于用户实现自定义类型,都默认继承自Object类,而Object类中提供了equal方法,而默认情况下调用的就是equal方法,但是该方法的比较规则是:没有比较引用变量引用对象的内容,而是直接比较引用变量的地址,但有些情况下该种比较就不符合题意。
// Object中equal的实现,可以看到:直接比较的是两个引用变量的地址
public boolean equals(Object obj) {
return (this == obj);
}有些情况下,需要比较的是对象中的内容,比如:向优先级队列中插入某个对象时,需要对按照对象中内容来调整堆,那该如何处理呢?
import java.util.Objects;
public 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) {
if(this == o){
return true;
}
if (o == null || getClass() != o.getClass())
return false;
Card card = (Card) o;
return rank == card.rank && Objects.equals(suit, card.suit);
}
}注意: 一般覆写 equals 的套路就是上面演示的
Comparble是JDK提供的泛型的比较接口类,源码实现具体如下:
public interface Comparable<E> {
// 返回值:
// < 0: 表示 this 指向的对象小于 o 指向的对象
// == 0: 表示 this 指向的对象等于 o 指向的对象
// > 0: 表示 this 指向的对象大于 o 指向的对象
int compareTo(E o);
}对用用户自定义类型,如果要想按照大小与方式进行比较时:在定义类时,实现Comparble接口即可,然后在类中重写compareTo方法
import java.util.Objects;
public 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 boolean equals(Object o) {
if(this == o){
return true;
}
if (o == null || getClass() != o.getClass())
return false;
Card card = (Card) o;
return rank == card.rank && Objects.equals(suit, card.suit);
}
@Override
public int compareTo(Card o) {
if(o == null){
return 1;
}
return this.rank - o.rank;
}
}Compareble是java.lang中的接口类,可以直接使用
按照比较器方式进行比较,具体步骤如下:
用户自定义比较器类,实现Comparator接口
public interface Comparator<T> {
// 返回值:
// < 0: 表示 o1 指向的对象小于 o2 指向的对象
// == 0: 表示 o1 指向的对象等于 o2 指向的对象
// > 0: 表示 o1 指向的对象等于 o2 指向的对象
int compare(T o1, T o2);
}注意:区分Comparable和Comparator。 Comparable 是调用比较方法的对象与其他对象的比较,Comparator是单独做一个比较器类,通过比较器来比较。
覆写Comparator中的compare方法
import java.util.Comparator;
class CardComparator implements Comparator<Card> {
// 根据数值比较,不管花色
// 这里我们认为 null 是最小的
@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;
}
}注意:Comparator是java.util 包中的泛型接口类,使用时必须导入对应的包。
import java.util.Comparator;
public class SuitComparator implements Comparator<Card> {
@Override
public int compare(Card o1, Card o2) {
return o1.suit.compareTo(o2.suit);
}
}集合框架中的PriorityQueue底层使用堆结构,因此其内部的元素必须要能够比大小,PriorityQueue采用了Comparble和Comparator两种方式。
所以说,既然默认情况下是小根堆,所以我们把比较器反着来写,并把自己新写的比较器在构造方法中传参传进去,那么就可以得到大根堆。
static class MyCmpBig implements Comparator<Integer>{
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
}
public static int[] smallestK(int[] arr, int k) {
if(arr == null || k == 0){
return new int[0];
}
PriorityQueue<Integer> ret = new PriorityQueue<>(arr.length,new MyCmpBig());
for (int i = 0; i < k; i++) {
ret.offer(arr[i]);
}
for (int i = k; i <arr.length ; i++) {
if(arr[i] < ret.peek()){
ret.poll();
ret.offer(arr[i]);
}
}
int[] retArr = new int[k];
for (int i = 0; i < k; i++) {
retArr[i] = ret.poll();
}
return retArr;
}再次强调Comparable和Comparator和区别: Comparable:是类自身实现的接口(位于java.lang包),表示类的对象 “天生具备可比较性”。实现Comparable的类需要重写compareTo(T o)方法,将比较逻辑嵌入类内部。 Comparator:是外部定义的比较器接口(位于java.util包),表示 “第三方制定的比较规则”。无需修改原类,只需创建一个实现Comparator的类,并重写compare(T o1, T o2)方法,将比较逻辑独立于原类之外。