我正在尝试在key上对TreeMap进行排序。 Key是一些具有int,List,String等的自定义DataStructure。 我期待排序的成员有一些重复。假设成员是Rank。超过1个对象可以具有相同的等级。
简化版示例:
注意:在CompareTo方法中,0不会故意返回0而不会忽略重复。(如果这不是重复的正确方法,请纠正我)
import java.util.TreeMap;
public class TreeTest {
public static void main(String[] args) {
TreeMap<Custom,String> t = new TreeMap<Custom,String>();
Custom c1 = new Custom();
c1.setName("a");
c1.setRank(0);
Custom c2 = new Custom();
c2.setName("b");
c2.setRank(1);
Custom c3 = new Custom();
c3.setName("c");
c3.setRank(0);
t.put(c1, "first");
t.put(c2, "Second");
t.put(c3, "Third");
System.out.println(t.keySet());
for(Custom c:t.keySet()){
System.out.println(t.get(c));
}
}
}
自定义对象
package com.example.ui;
public class Custom implements Comparable<Custom>{
int rank;
String name;
public int getRank() {
return rank;
}
public void setRank(int rank) {
this.rank = rank;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + rank;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Custom other = (Custom) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (rank != other.rank)
return false;
return true;
}
// 0 is not returned intentionally to NOT ignore duplicates.
public int compareTo(Custom o) {
if(o.rank>this.rank)
return 1;
if(o.rank==this.rank)
return -1;
return -1;
}
}
输出:
[com.example.ui.Custom@fa0, com.example.ui.Custom@fbe, com.example.ui.Custom@f80] null null null
预期: 第一,第二,第三,分别基于等级0,1,0。
我查看了Google上的几个例子。其中大部分是使用具有原始数据类型的键或值的TreeMap排序的基本用法,但在排序成员时没有重复项 是自定义密钥DataStructure的一部分。
请帮帮忙?
答案 0 :(得分:7)
问题在于compareTo
的实施与等号不一致,TreeMap
要求等同。来自API文档:
请注意由排序地图维护的排序(无论是否为 提供显式比较器)必须与equals一致 这个有序映射是为了正确实现Map接口。
一种可能的一致实现方式是首先按等级进行比较,然后按等级进行比较,如果等级值相等。对于具有相同排名和相同名称的两个Custom实例,您不应期望将它们作为键存储在同一个Map中 - 这违反了Map的合同。
public int compareTo(Custom o) {
int ret = this.rank - o.rank;
// Equal rank so fall back to comparing by name.
if (ret == 0) {
ret = this.name.compareTo(o.name);
}
return ret;
}
答案 1 :(得分:0)
如前所述,你的equals和compareTo的实现彼此不一致。如果我正确地阅读了您的问题,您需要的是保留具有相同密钥的重复项。我建议您查看Google Guava馆藏的TreeMultimap。它为每个值对象创建集合容器,以保留具有相同键的不同值。 e.g。
treeMultimap.put ("rank1", "Joe");
treeMultimap.put ("rank1", Jane");
treeMultimap.get ("rank1"); // Set("Joe","Jane");
此数据结构中的约束是K,V对必须是唯一的。也就是说,你不能在Multimap中插入两次(“rank1”,“Joe”)。
一个重要的注意事项:您之所以看到如此多的Map示例,使用简单类型,特别是字符串,是因为地图中的键必须是不可变的。对象的equals和hashcode值在用作映射中的键时不得更改。翻译为您的示例,您无法执行customObject.setRank(...)
并在将其用作键时更新排名值。为此,首先需要删除密钥及其值,更新密钥然后重新插入。
答案 2 :(得分:0)
您也可以通过将Comparator实现为匿名内部类型并重写compare()来返回所需的比较来实现。
public class TreeMaps
{
public static void main(String[] args)
{
Custom c1 = new Custom(1,"A");
Custom c2 = new Custom(3,"C");
Custom c3 = new Custom(2,"B");
TreeMap<Custom , Integer > tree = new TreeMap<Custom, Integer> (new Comparator<Custom>() {
@Override
public int compare(Custom o1, Custom o2) {
return o1.rank - o2.rank;
}
});
tree.put(c1, 1);
tree.put(c2, 2);
tree.put(c3, 3);
System.out.println(tree);
}
}
class Custom
{
int rank ;
String name ;
public Custom(int rank , String name) {
this.rank = rank ;
this.name = name ;
}
@Override
public String toString()
{
return "Custom[" + this.rank + "-" + this.name + "]" ;
}
}