前言
因为在上一章优先级队列中有讲到:优先级队列在插入元素时有个要求:插入的元素直接必须要能够进行比较
所以我们以优先级队列为例,来详细讲解Java中对象比较需要注意的细节
equals()方法
- 在Java中,所有类默认继承
Object
这个类,而equals()
方法是Object
类中的默认方法。 - 查看其源码:
// Class: Ojbect
public boolean equals(Object obj) {
return (this == obj);
}
- 可以发现,这个方法直接比较的是两个引用变量的地址,而
==
符号默认情况下调用的就是equals()
方法。这种方法的缺陷是:没有比较引用变量对象的内容,而是直接比较引用变量的地址,在这种情况下,显然满足不了大多数的需求,所以我们可以在类中对equals()
方法进行重写,以满足我们的需求。- 但是这种重写相当于将
equals()
方法定死了,以后要修改只能在源码上修改,不够灵活
- 但是这种重写相当于将
注意:重写 equals()
方法要点:
- 如果是同一对象,返回 true
- 如果传入的引用变量为 null,返回 false
- 如果传入的对象类型与所调用
equals()
的引用变量对象类型不匹配,返回 false - 基本类型可以直接使用
==
符号进行比较,但引用变量类型需要调用equals()
方法进行比较
Comparable接口比较
comparable 释义:可比较的
所以若某个类实现了 Comparable 接口,说明这个类的实例对象是可以相比较的,拥有了和别人比较的能力
实现 Comparable 接口的类需要重写其中的 compareTo()
方法(Comparable 接口中只有这一个抽象方法)
其源码如下:
public int compareTo(T o);
- 在优先级队列源码中
offer()
这个方法实际调用的是以下方法:
private void siftUp(int k, E x) {
if (comparator != null)
// 1.如果传入比较器,调用带有 Comparator 的方法
siftUpUsingComparator(k, x);
else
// 2.如果没有传入比较器,调用带有 Comparable 的方法
siftUpComparable(k, x);
}
private void siftUpComparable(int k, E x) {
// 3.注意这里的强转
Comparable<? super E> key = (Comparable<? super E>) x;
while (k > 0) {
int parent = (k - 1) >>> 1;
Object e = queue[parent];
// 4.调用已经重写好的 compareTo 方法
if (key.compareTo((E) e) >= 0)
break;
queue[k] = e;
k = parent;
}
queue[k] = key;
}
注意 注释 中的第 4 点:
- 我们现在提供两个数据进入优先级队列中:
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
priorityQueue.offer(1);
priorityQueue.offer(10);
- 那么 key 中的数据就为 10。我们在 PriorityQueue 传入的是
Integer
类型,key的类型属于Integer
,说明 key 调用的是Integer
所重写的compareTo()
方法
Integer所重写的 compareTo()
方法源码:
public int compareTo(Integer anotherInteger) {
return compare(this.value, anotherInteger.value);
}
public static int compare(int x, int y) {
return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
- 可以看到,这个方法也是写 “死” 的方法,一旦写成,是无法修改的,你能去修改Java中的源码吗?显然是不可以的
- 这也就引出了我所表达的重点:实现Comparable接口的类所重写的
compareTo()
方法,与equals()
方法一样,都是一开始就写 “死” 的了,不够灵活,大部分情况下可以使用这些自带的方法,但某些特殊情况再使用这种方法就难以满足某些需求 - 所以在Java中又引出了 Comparator 接口
Comparator接口(比较器)
-
在优先级队列的构造方法中,是支持用户传入一个 “比较器” 参数的
-
如果传入 “比较器” 之后再调用
offer()
方法,就会走第一个if语句,然后按照你自己写的 “比较器” 的逻辑进行比较。比如你自己写的比较器可以比较 “名字”或者其他 非基本类型 变量
private void siftUp(int k, E x) {
if (comparator != null)
// 1.如果传入比较器,调用带有 Comparator 的方法
siftUpUsingComparator(k, x);
else
// 2.如果没有传入比较器,调用带有 Comparable 的方法
siftUpComparable(k, x);
}
- 由此可见,使用 ”比较器“ 就比较灵活,是用户自定义的 比较逻辑
三种方式的对比
总结
- 简单来说,
equals()
方法与Comparable
接口有点类似于在打游戏时官方给你的游戏设置,官方会给你一个默认设置,如果你自己使用着不习惯或是不舒服,你可以自行修改,自行修改就相当于这里的实现Comparator
接口,用户自行定义自己的比较逻辑