【问题标题】:TreeMap returning null for value that should exist for some object keysTreeMap 为某些对象键应该存在的值返回 null
【发布时间】:2012-01-16 22:01:59
【问题描述】:

我对我们定义了自定义键对象的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 的方式。我愿意接受有关如何改进此代码的建议。谢谢

【问题讨论】:

  • 你是否彻底测试过 Key.equals() 方法?
  • 如果您有多个键且两个字段为空,则会得到一棵混合树。
  • is:if(StringUtils.isNotBlank(alwaysPopulated) &amp;&amp; StringUtils.isNotBlank(aKey.getAlwaysPopulated())) 曾经是假的吗?
  • 您最好在设置字段时验证字段,而不是在每次比较时重新验证。
  • alwaysPopulated 永远不会为空,所以不,它不会超过 if 条件。

标签: java sorting map treemap


【解决方案1】:

我认为问题在于,如果 sometimesPopulated 值中的任何一个为空白或 alwaysPopulated 值中的任何一个为空白,则您从 compareTo 返回 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;
}

【讨论】:

  • 这很有趣,我来看看这段代码。我确实有一个问题,你为什么要做 if(result != 0) return result?我试图弄清楚 if(this.equals(aKey)) return 0 在开头是否正确...
  • 我使用result != 0,因为如果它是== 0,那么我们想要下拉并使用alwaysPopulated 执行相同的逻辑。如果是!= 0,那么我们可以立即返回比较结果。
【解决方案2】:

您的比较器违反了transitivity requirement

考虑三个对象:

  1. 对象AsometimesPopulated="X"alwaysPopulated="3"
  2. 对象BsometimesPopulated="Y"alwaysPopulated="1"
  3. 对象CsometimesPopulated 为空白且alwaysPopulated="2"

使用您的比较器,A&lt;BB&lt;C。传递性要求A&lt;C。但是,使用您的比较器,A&gt;C

由于比较器不履行其合同,TreeMap 无法正确完成其工作。

【讨论】:

  • 谢谢,我是这么想的,但没能像你说的那样简洁。鉴于这就是问题所在,您有什么建议可以在不改变确定平等方式的情况下改进这一点吗?
  • @JavaKungFu:你能用HashMap吗,因为它只需要hashCode和比较相等吗?
  • 不幸的是,没有,简而言之,如果有时Populated 不存在,我们会遇到类似的问题,它会被散列到错误的“桶” - 假设我们不使用 42 作为哈希码:)
【解决方案3】:

我认为您的 equals、hashCode 和 compareTo 方法应该只使用始终填充的字段。这是确保始终在地图中找到相同对象的唯一方法,无论其可选字段是否设置。

第二个选项,您可以编写一个实用方法来尝试在映射中查找值,如果未找到值,则使用相同的键再次尝试,但设置(或不设置)可选字段。

【讨论】:

  • 不幸的是,这不是代码的设计方式,我们需要我们的 equals 方法来按照它的编写方式运行。
【解决方案4】:

我认为问题在于您将两个具有两个空白字段的键视为彼此大于彼此,这可能会混淆结构。

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;
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-18
    • 1970-01-01
    • 2021-02-09
    • 1970-01-01
    相关资源
    最近更新 更多