粉丝答疑:为什么我写的treeMap.get(key)获取的值为null?

举报
Java实用技术@Pandas 发表于 2023/03/31 08:23:06 2023/03/31
【摘要】 昨天粉丝在后台问了我一个问题,他写了一个TreeMap,但是通过key获取的值是null,百思不得其姐😔,也百思不得其寐,让我们来一看究竟。

image.png

昨天粉丝在后台问了我一个问题,他写了一个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站均有视频解说,欢迎来玩。
如果你觉得这篇文章有用,别忘了点赞+关注,一起进步!

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。