TreeMap调用get()返回null但HashMap正常的原因解析及代码示例
解析TreeMap.get()返回null但HashMap正常工作的问题
咱们直接拆解这个问题:你遇到的差异,核心根源是HashMap和TreeMap判断键是否相等的逻辑完全不同,而你的Employees类里的比较方法实现犯了致命错误,刚好踩中了TreeMap的规则。
先搞懂HashMap和TreeMap的核心差异
- HashMap:依赖
equals()判断两个键是否相等,hashCode()只是用来快速定位键所在的桶(性能优化用)。你的equals()方法是按id比较的,所以哪怕hashCode()固定返回1(这会导致所有键都在同一个桶里,性能很差,但不影响正确性),HashMap依然能正确区分e1和e2,get()自然能拿到对应的值。 - TreeMap:作为SortedMap的实现,它完全不依赖equals/hashCode,而是靠
Comparable接口的compareTo()方法(或者你传入的外部Comparator)来判断键的关系:- 当
compareTo(a, b) < 0:a排在b前面 - 当
compareTo(a, b) > 0:a排在b后面 - 当
compareTo(a, b) == 0:TreeMap认为a和b是同一个键
- 当
你的代码里的致命错误
看你Employees类里的compareTo()和compare()实现:
@Override public int compareTo(Employees o) { return 1; } @Override public int compare(Employees o1, Employees o2) { return 1; }
不管传入什么对象,永远返回1!这会导致TreeMap的逻辑彻底混乱:
- 当你
put(e1, 1)时,TreeMap把e1作为根节点。 - 当你
put(e2, 2)时,TreeMap用e2.compareTo(e1)得到1,会认为e2比e1大,把它放在e1的右子树。 - 当你调用
m.get(e2)时,TreeMap会从根节点e1开始比较:e2.compareTo(e1)返回1,于是往右边找;但右子树的节点是e2,此时用e2.compareTo(e2)还是返回1?你的方法不管和谁比都返回1,所以TreeMap会一直认为当前节点比要找的键小,继续往右找,最后找不到任何compareTo返回0的节点,只能返回null。
更糟的是,这种错误的比较逻辑还会导致TreeMap无法维护正确的有序结构,甚至可能出现遍历异常或者无限循环。
正确的实现方式
你需要根据业务逻辑实现合理的compareTo()方法,而且最好和equals()的逻辑保持一致(这是Java集合框架的约定:如果a.compareTo(b) == 0,那么a.equals(b)必须返回true,否则会出现逻辑矛盾)。
比如按照你的equals()是按id比较,那compareTo()可以这么写:
@Override public int compareTo(Employees o) { // 按id升序排序,和equals逻辑对齐 return Integer.compare(this.id, o.id); }
另外,你的Employees类没必要同时实现Comparable和Comparator接口——除非你需要把它作为外部比较器使用。一般来说,只实现Comparable让类自身具备排序能力就够了。
还有,你的hashCode()也应该和equals()一致,这样HashMap的性能会正常:
@Override public int hashCode() { return Integer.hashCode(id); }
修改后的验证
修改之后再运行代码:
m.put(e1, 1)返回null(第一次插入,没有旧值)m.put(e2, 2)返回null(e2的id和e1不同,是新键)m.get(e2)返回2,完全正常。
如果此时你创建一个和e2 id相同的对象:
Employees e3 = new Employees("def", 12);
那么m.put(e3, 3)会返回2(TreeMap认为e3和e2是同一个键,替换旧值),m.get(e3)会返回3,这和HashMap的行为完全一致,因为equals()也认为e2和e3相等。
内容的提问来源于stack exchange,提问作者GaurZilla
相关产品推荐
相关产品推荐

