我遇到了TreeMap
的问题,我们为其定义了自定义密钥对象。问题是,在将几个对象放入地图后,尝试使用用于放置在地图上的相同密钥进行检索后,我得到一个空值。我相信这是因为我们在密钥上有2个数据点。始终填充一个值,并且不总是填充一个值。所以问题似乎在于使用compareTo
和equals
。不幸的是,我们的密钥如何确定相等性的业务需求需要以这种方式实现。
我认为最好用代码说明。
public class Key implements Comparable<Key> {
private String sometimesPopulated;
private String alwaysPopulated;
public int compareTo(Key aKey){
if(this.equals(aKey)){
return 0;
}
if(StringUtils.isNotBlank(sometimesPopulated) && StringUtils.isNotBlank(aKey.getSometimesPopulated())){
return sometimesPopulated.compareTo(aKey.getSometimesPopulated());
}
if(StringUtils.isNotBlank(alwaysPopulated) && StringUtils.isNotBlank(aKey.getAlwaysPopulated())){
return alwaysPopulated.compareTo(aKey.getAlwaysPopulated());
}
return 1;
}
public boolean equals(Object aObject){
if (this == aObject) {
return true;
}
final Key aKey = (Key) aObject;
if(StringUtils.isNotBlank(sometimesPopulated) && StringUtils.isNotBlank(aKey.getSometimesPopulated())){
return sometimesPopulated.equals(aKey.getSometimesPopulated());
}
if(StringUtils.isNotBlank(alwaysPopulated) && StringUtils.isNotBlank(aKey.getAlwaysPopulated())){
return alwaysPopulated.equals(aKey.getAlwaysPopulated());
}
return false;
}
因此,在将一些项目放在地图上后尝试从地图上获取值时会出现问题。
Map<Key, String> map = new TreeMap<Key, String>();
Key aKey = new Key(null, "Hello");
map.put(aKey, "world");
//Put some more things on the map...
//they may have a value for sometimesPopulated or not
String value = map.get(aKey); // this = null
那么为什么在将它放入之后值为null?我认为TreeMap
使用的算法是以不一致的方式对地图进行排序,因为我使用compareTo
和equals
的方式。我对如何改进此代码的建议持开放态度。感谢
答案 0 :(得分:4)
您的比较器违反transitivity requirement。
考虑三个对象:
A
:sometimesPopulated="X"
和alwaysPopulated="3"
。B
:sometimesPopulated="Y"
和alwaysPopulated="1"
。C
:sometimesPopulated
为空alwaysPopulated="2"
。使用比较器A<B
和B<C
。及物性需要A<C
。但是,使用比较器A>C
。
由于比较器未履行合同,TreeMap
无法正常完成工作。
答案 1 :(得分:1)
我认为问题在于,如果compareTo
值中的任何一个为空,或者sometimesPopulated
值中的任何一个为空,则您从alwaysPopulated
返回1。请记住compareTo
可以被认为是返回减法运算的值而你的不是传递的。 (a - b)即使在a = = b。
如果aKey
sometimesPopulated
不为空且本地sometimesPopulated
为空,我会返回-1。如果它们相同,那么我会对alwaysPopulated
执行相同的操作。
我认为你的逻辑应该是这样的:
public int compareTo(Key aKey){
if(this.equals(aKey)){
return 0;
}
if (StringUtils.isBlank(sometimesPopulated)) {
if (StringUtils.isNotBlank(aKey.getSometimesPopulated())) {
return -1;
}
} else if (StringUtils.isBlank(aKey.getSometimesPopulated())) {
return 1;
} else {
int result = sometimesPopulated.compareTo(aKey.getSometimesPopulated());
if (result != 0) {
return result;
}
}
// same logic with alwaysPopulated
return 0;
}
答案 2 :(得分:1)
我认为问题在于你正在处理两个密钥,两个空白字段彼此之间的距离可能会使结构混乱。
class Main {
public static void main(String... args) {
Map<Key, String> map = new TreeMap<Key, String>();
Key aKey = new Key(null, "Hello");
map.put(aKey, "world");
//Put some more things on the map...
//they may have a value for sometimesPopulated or not
String value = map.get(aKey); // this = "world"
System.out.println(value);
}
}
class Key implements Comparable<Key> {
private final String sometimesPopulated;
private final String alwaysPopulated;
Key(String alwaysPopulated, String sometimesPopulated) {
this.alwaysPopulated = defaultIfBlank(alwaysPopulated, "");
this.sometimesPopulated = defaultIfBlank(sometimesPopulated, "");
}
static String defaultIfBlank(String s, String defaultString) {
return s == null || s.trim().isEmpty() ? defaultString : s;
}
@Override
public int compareTo(Key o) {
int cmp = sometimesPopulated.compareTo(o.sometimesPopulated);
if (cmp == 0)
cmp = alwaysPopulated.compareTo(o.alwaysPopulated);
return cmp;
}
}
答案 3 :(得分:0)
我认为你的equals,hashCode和compareTo方法应该只使用总是填充的字段。这是确保在地图中始终可以找到相同对象的唯一方法,无论其是否设置了可选字段。
第二个选项,您可以编写一个试图在地图中查找值的实用程序方法,如果没有找到值,则使用相同的键再次尝试,但使用(或不使用)可选字段集。