TreeMap为某些对象键应存在的值返回null

时间:2011-12-09 16:16:42

标签: java sorting map treemap

我遇到了TreeMap的问题,我们为其定义了自定义密钥对象。问题是,在将几个对象放入地图后,尝试使用用于放置在地图上的相同密钥进行检索后,我得到一个空值。我相信这是因为我们在密钥上有2个数据点。始终填充一个值,并且不总是填充一个值。所以问题似乎在于使用compareToequals。不幸的是,我们的密钥如何确定相等性的业务需求需要以这种方式实现。

我认为最好用代码说明。

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使用的算法是以不一致的方式对地图进行排序,因为我使用compareToequals的方式。我对如何改进此代码的建议持开放态度。感谢

4 个答案:

答案 0 :(得分:4)

您的比较器违反transitivity requirement

考虑三个对象:

  1. 对象AsometimesPopulated="X"alwaysPopulated="3"
  2. 对象BsometimesPopulated="Y"alwaysPopulated="1"
  3. 对象CsometimesPopulated为空alwaysPopulated="2"
  4. 使用比较器A<BB<C。及物性需要A<C。但是,使用比较器A>C

    由于比较器未履行合同,TreeMap无法正常完成工作。

答案 1 :(得分:1)

我认为问题在于,如果compareTo值中的任何一个为空,或者sometimesPopulated值中的任何一个为空,则您从alwaysPopulated返回1。请记住compareTo可以被认为是返回减法运算的值而你的不是传递的。 (a - b)即使在a = = b。

时也可以==(b - a)

如果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方法应该只使用总是填充的字段。这是确保在地图中始终可以找到相同对象的唯一方法,无论其是否设置了可选字段。

第二个选项,您可以编写一个试图在地图中查找值的实用程序方法,如果没有找到值,则使用相同的键再次尝试,但使用(或不使用)可选字段集。