粉丝答疑:为什么我写的treeMap.get(key)获取的值为null?
昨天粉丝在后台问了我一个问题,他写了一个TreeMap,但是通过key获取的值是null,百思不得其姐😔,也百思不得其寐,让我们来一看究竟。
📌问题复现
先定义一个Users对象,里面实现了Comparable
接口
@Data
@AllArgsConstructor
public class Users implements Comparable<Users> {
private String name;
private int age;
@Override
public int compareTo(Users users) {
// 定义比较规则,正数-大,负数-小,0-相等
if (this.age > 0) {
return 1;
}
if (this.age == 0) {
//年龄相等再比较名字
return this.name.compareTo(users.getName());
}
return -1;
}
}
写一个TreeMap测试类。
/**
* the TestTree
* @author Java实用技术手册
* @date 2022-07-26
*/
public class TestTree {
public static void main(String[] args) {
// 实例化TreeMap, 使用Users, 类型来比较外部比较器.红黑树结构
Map<Users, String> map = new TreeMap<>();
Users u1 = new Users("Tom", 19);
Users u2 = new Users("Jack", 20);
map.put(u1, "Tom");
map.put(u2, "Jack");
Set<Users> keys = map.keySet();
for (Users key : keys) {
System.out.println(key + "----" + map.get(key));
}
}
}
运行结果
![[java6.png]]
🎯原因
TreeMap基础知识
我们知道TreeMap是一个有序key-value集合,它的排序是通过key的自然排序或者指定的比较器实现的。底层数据结构是红黑树。
用官方的说法是:
TreeMap 有两种排序:
- 自然排序: TreeMap 的所有Key必须实现 Comparable 接口,而且所有的key应该是同一个类的对象,否则将会抛出 ClassCastException 异常。
- 定制排序: 创建TreeMap 时,传入一个Comparator对象,该对象负责对TreeMap中的所有key进行排序。采用定制排序时不要求Map的key实现 Comparable接口。
用人话就是:
- 自然排序:key本身可以按照abcd字母排序,或者key本身实现 Comparable 接口,通过
k1.compareTo(k2)
能确定谁先谁后的规则也行。 - 定制排序:自己写一个比较器Comparator,其实也是为了通过
k1.compareTo(k2)
能确定谁先谁后。
数据存储
![[hongheishu 1.gif]]
这里我推荐一个美国旧金山大学的数据结构可视化网页。https://www.cs.usfca.edu/~galles/visualization/Algorithms.html
你可以通过手动添加数据,查看数据结构中的变化过程,比如上述红黑树,你可以通过页面看到根和叶子变化情况,从而加深理解。
源码探索
// java.util.TreeMap
public V get(Object key) {
Entry<K,V> p = getEntry(key);
return (p==null ? null : p.value);
}
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; // K小于P,继续往左查找
else if (cmp > 0)
p = p.right; // K大于P,继续往右查找
else
return p; // 终于查找到了
}
return null; // 查找到叶子边缘还是找不到就算了吧
}
该同学写的比较器,只根据age判断,任何一个age>0的users进来,都会返回1,也就是会让TreeMap继续往右查找,最后就是查到20右边的NULL🍀。
这个同学的想法是对的,但是写法不对,比较器,应该跟传入的对象比较,而不是跟自己比较。
@Override
public int compareTo(Users users) {
// 定义比较规则,正数-大,负数-小,0-相等
if (this.age > 0) {
return 1;
}
if (this.age == 0) {
return this.name.compareTo(users.getName());
}
return -1;
}
修改
知道了上面的问题,修改也很简单,就是修改下比较器。跟传入的users.age比较,如果age都相等了,再比较name。
@Override
public int compareTo(Users users) {
// 定义比较规则,正数-大,负数-小,0-相等
if (this.age > users.getAge()) {
return 1;
}
if (this.age == users.getAge()) {
return this.name.compareTo(users.getName());
}
return -1;
}
修改后运行结果:
Users(name=Tom, age=19)----Tom
Users(name=Jack, age=20)----Jack
📝总结
使用TreeMap时,一定要记住key需要有一个稳定的比较器!什么是稳定?就是 不论A、B的传入顺序有什么变化,两次比较的结果都一样,即 A > B === B < A。
今天的内容就到这里,希望对你以后开发有帮助。
我是Pandas,专注Java编程实用技术分享,公众号「
Java实用技术手册
」和B站均有视频解说,欢迎来玩。
如果你觉得这篇文章有用,别忘了点赞+关注,一起进步!
- 点赞
- 收藏
- 关注作者
评论(0)