上一章中关于PriorityQueue的使用要注意:
1. PriorityQueue中放置的元素必须要能够比较大小,不能插入无法比较大小的对象,否则会抛出
ClassCastException异常
2. 不能插入null对象,否则会抛出NullPointerException
3. 没有容量限制,可以插入任意多个元素,其内部可以自动扩容
4. 插入和删除元素的时间复杂度为
5. PriorityQueue底层使用了堆数据结构
6. PriorityQueue默认情况下是小堆---即每次获取到的元素都是最小的元素
优先级队列在插入元素时有个要求:插入的元素不能是null或者元素之间必须要能够进行比较,比如Integer,那优先级队列中能否插入自定义类型对象呢?
这里就会涉及到元素之间的比较。
1. 元素比较
对于元素,我们分为两大类,一类为基本数据类型,另一类为引用类型。
基本数据类型很好比较,整型,浮点型,布尔型都可以比较其大小。
那么引用类型如何比较呢?
比如两个人,两栋楼?
例如:
我们创建一个扑克牌类:
class Card { public int rank; // 数值 public String suit; // 花色 public Card(int rank, String suit) { this.rank = rank; this.suit = suit; } }
从编译结果可以看出,Java中引用类型的变量不能直接按照 > 或者 < 方式进行比较。 那为什么==可以比较?
因为:对于用户实现自定义类型,都默认继承自Object类,而Object类中提供了equal方法,而==默认情况下调用的就是equal方法,但是该方法的比较规则是:没有比较引用变量引用对象的内容,而是直接比较引用变量的地址,但有些情况下该种比较就不符合题意。
Object中equal的实现,可以看到:直接比较的是两个引用变量的地址:
2. 对象的比较
有些情况下,需要比较的是对象中的内容,比如:向优先级队列中插入某个对象时,需要对按照对象中内容来调整堆,那该如何处理呢?
我们一共有三种方法来实现:重写equals()方法 、 Comparble接口 、 比较器
2.1 重写equals
这是最好理解的一种比较方法。
例如:
注意基本类型可以直接比较,但引用类型最好调用其equal方法。
虽然重写equals方法很好用,但是却有个致命缺陷,它只能比较是否相等,但是却不能比较大小。
2.2 Comparble接口类的比较
Comparble是JDK提供的泛型的比较接口类,源码实现具体如下:
Comparable接口中只提供了compareTo方法,
返回值如下图:
我们还是以上面的扑克为例:
来看看实际效果:
2.3 比较器
Comparator 的简介:
Comparator 是比较器接口。
我们若需要控制某个类的次序,而该类本身不支持排序(即没有实现Comparable接口);那么,我们可以建立一个“该类的比较器”来进行排序。这个“比较器”只需要实现Comparator接口即可。也就是说,我们可以通过“实现Comparator类来新建一个比较器”,然后通过该比较器对类进行排序。
Comparator 的定义:
Comparator 接口仅仅只包括两个函数,它的定义如下:
int compare(T o1, T o2); boolean equals(Object obj);
说明:
若一个类要实现Comparator接口:它一定要实现compareTo(T o1, T o2) 函数,但可以不实现 equals(Object obj) 函数。
为什么可以不实现 equals(Object obj) 函数呢? 因为任何类,默认都是已经实现了equals(Object obj)的。 Java中的一切类都是继承于java.lang.Object,在Object.java中实现了equals(Object obj)函数;所以,其它所有的类也相当于都实现了该函数。
int compare(T o1, T o2) 是“比较o1和o2的大小”。返回“负数”,意味着“o1比o2小”;返回“零”,意味着“o1等于o2”;返回“正数”,意味着“o1大于o2”。
我们来依旧使用上面的例子:
结构如下:
三种方式的比较:
覆写的方法 | 说明 |
Object.equals | 因为所有类都是继承自 Object 的,所以直接覆写即可,不过只能比较相等与 否 |
Comparable.compareTo | 需要手动实现接口,侵入性比较强,但一旦实现,每次用该类都有顺序,属于 内部顺序 |
Comparator.compare | 需要实现一个比较器对象,对待比较类的侵入性弱,但对算法代码实现侵入性 强 |