Scala TreeMap仅存一个元素问题求助:与Java实现结果差异
这是个很典型的TreeMap键相等性判断的坑,核心差异出在Scala TreeMap的判断逻辑,以及你写的Java Comparator的错误实现上,咱们慢慢捋清楚:
1. Scala代码的问题:排序规则直接导致键被判定为同一
Scala里的TreeMap(不管可变还是不可变),是完全依赖传入的Ordering来判断键是否相等的——只要Ordering.compare(a, b)返回0,就会把a和b当成同一个键,后续的put操作直接覆盖之前的值。
你写的Ordering.by(x => x._1)只拿元组的第一个元素做排序依据,那当两个元组的第一个元素相等时(比如(9,21)和(9,10)),compare方法会返回0,TreeMap自然就认为这俩是同一个键,第二个put直接把第一个值覆盖了,最终size就是1。
如果想让第一个元素相等时,用第二个元素区分开,得修改排序规则,比如:
// 方法1:同时把两个元素作为排序键 val a = scala.collection.mutable.TreeMap[(Int, Int), String]()( Ordering.by[(Int, Int), (Int, Int)](x => (x._1, x._2)) ) // 方法2:直接用元组默认的Ordering(更简洁) val a = scala.collection.mutable.TreeMap[(Int, Int), String]()( Ordering.Tuple2(Ordering.Int, Ordering.Int) )
这样修改后,两个键就会被判定为不同,最终size会变成2。
2. Java代码的“假象”:Comparator实现错误才保留了两个元素
你写的Java Comparator其实是不符合规范的,先回忆一下Comparator.compare的返回规则:
- 返回负数:表示前者排在后者前面
- 返回0:表示两者相等
- 返回正数:表示前者排在后者后面
而你的实现:
@Override public int compare(Tuple arg0, Tuple arg1) { if (arg0.a > arg1.a) { return arg0.a; } else return arg1.a; }
当两个Tuple的a相等时(比如都是100),会返回arg1.a也就是100(正数),而不是规范要求的0,所以Java的TreeMap会认为这两个键不相等,因此两个元素都被保留,size是2。
如果把Java的Comparator改成正确的、只比较第一个元素的实现:
@Override public int compare(Tuple arg0, Tuple arg1) { return arg0.a.compareTo(arg1.a); }
这时候再运行,你会发现Java的TreeMap size也会变成1,和Scala之前的结果一致——因为此时两个键会被判定为相等,第二个put覆盖了第一个值。
总结一下
- Scala的TreeMap严格遵循
Ordering的结果判断键相等性,只要compare返回0就视为同一键 - 你之前的Java代码是因为Comparator实现错误,才出现了和Scala不同的结果,并非Java和Scala的TreeMap逻辑有差异
- 要让Scala的TreeMap保留两个元素,必须修改
Ordering,让它同时比较元组的两个元素
内容的提问来源于stack exchange,提问作者Guda uma shanker

