Java中实现类的自定义比较功能,可以通过实现Comparable,或者Comparator,前者是一个内比较器,后者是一个外比较器,但无论是哪种在实现比较方法时,都应该充分考虑各种情况:
- 比较者大于被比较者(也就是compareTo方法里面的对象),那么返回正整数
- 比较者等于被比较者,那么返回0
- 比较者小于被比较者,那么返回负整数
假如只考虑了其中的两种情况,会有什么影响了?
Bad Case
考虑下面的实现,在调用TreeSet或者TreeMap的containsKey时会有什么影响了?
@Data
@EqualsAndHashCode
class ImgSize implements Comparable<ImgSize> {
private int width;
private int height;
public ImgSize(int width, int height) {
this.width = width;
this.height = height;
}
@Override
public int compareTo(ImgSize obj) {
if (this.width < obj.width) {
return -1;
} else if (this.height < obj.height) {
return -1;
} else {
return 1;
}
}
}
Map<ImgSize, String> pics = new TreeMap<>();
pics.put(new ImgSize(3,4), "Pic_A");
pics.put(new ImgSize(1,3), "Pic_B");
pics.put(new ImgSize(1,2), "Pic_C");
for(Map.Entry entry:pics.entrySet()) {
System.out.println(entry);
}
System.out.println(pics.containsKey(new ImgSize(1,2)));
// 输出
ImgSize(width=1, height=2)=Pic_C
ImgSize(width=1, height=3)=Pic_B
ImgSize(width=3, height=4)=Pic_A
false
尽管实现了排序功能,但是containsKey方法返回的false,这是为何了?不妨看下它的实现
public boolean containsKey(Object key) {
return getEntry(key) != null;
}
final Entry<K,V> getEntry(Object key) {
// Offload comparator-based version for sake of performance
if (comparator != null)
return getEntryUsingComparator(key);
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
Entry<K,V> p = root;
while (p != null) {
int cmp = k.compareTo(p.key);
if (cmp < 0)
p = p.left;
else if (cmp > 0)
p = p.right;
else
return p;
}
return null;
}
本以为containsKey是根据hashCode和equals方法来实现的,已lombok注解已经实现了这两个方法,然而containsKey实际是根据比较器实现的,如果自定义的比较器中没有相等的实现,containsKey是不会正确返回预期的结果的。
修正
@Data
@EqualsAndHashCode
class ImgSize implements Comparable<ImgSize> {
private int width;
private int height;
public ImgSize(int width, int height) {
this.width = width;
this.height = height;
}
@Override
public int compareTo(ImgSize obj) {
if (this.width < obj.width) {
return -1;
} else if (this.width == obj.width) {
return (this.height < obj.height ? -1:(this.height == obj.height ? 0 : 1));
} else {
return 1;
}
}
}