【问题标题】:Why my equals method won't work?为什么我的 equals 方法不起作用?
【发布时间】:2010-07-27 13:58:42
【问题描述】:
assertEquals(def.getMengs(), exp.getMengs());

失败,报告: 预期:java.util.HashSet 但是:java.util.HashSet

确实,通过调试器,我看到两个集合都只包含一个含义,其中 objId = 1。 我希望含义类(@Entity)中的以下代码能够保证上述工作。

@Override
public boolean equals(Object object) {
   if (!(object instanceof Meaning)) {
        return false;
   }
   Meaning other = (Meaning) object;
   if (other != null && objId == other.objId) return true;
   return false;
}

@Override
public int hashCode() {
    int hash = 7;
    hash = 67 * hash + this.objId;
    return hash;
}

确实,这个测试通过了:

 db.insert(admin);
    final Meaning meng = new Meaning(admin, new Expression("essen"));
    meng.setObjId(11);
    final Meaning meng1  = new Meaning(admin, new Expression("mangiare"));
    meng1.setObjId(11);
    assertEquals(meng,meng1);

那么我的问题可能是什么? 它们都是 HashSet,它们的大小相同,并且它们内部的对象相等。确实

            assertEquals(def.getMengs().iterator().next(), exp.getMengs().iterator().next());

在它通过之前。然而这不会(但我不知道为什么):

assertTrue(def.getMengs().containsAll(exp.getMengs()));

所以,这就是问题所在。

这是测试代码:

try{
            db.insertWords(toEnumMap(mengs[i],admin));
        }catch(Exception e){
            fail(e.getMessage());
        }
        final Expression exp = db.get(Expression.class, mengs[i][0]);
        testGender(exp, mengs[i][2]);

        final Expression def = db.get(Expression.class, mengs[i][1]);
        assertNotNull(def);

        assertEquals(def.getMengs().iterator().next(), exp.getMengs().iterator().next());
        assertEquals(exp.getMengs().size(), def.getMengs().size());
        assertTrue(def.getMengs().containsAll(def.getMengs()));
        assertTrue(def.getMengs().containsAll(exp.getMengs()));
        assertEquals(def.getMengs(), exp.getMengs());

db.get 只是包装了 em.find。 InsertWords 应该坚持 def 和 exp。

public void insertWords(EnumMap<Input, MemoEntity> input) throws MultipleMengsException {
    insert(input.get(Input.expression)); //INSERT OR IGNORE
    final boolean isNewDef = insert(input.get(Input.definition));

    final Expression def = get(Expression.class, input.get(Input.definition).getId());
    final Expression exp = get(Expression.class, input.get(Input.expression).getId());
    final MUser usr = get(MUser.class, input.get(Input.user).getId());

    final Set<Meaning> mengs = getMengs(usr,def,isNewDef);
    if (mengs == null) {//is new to the user
        final Meaning meng = new Meaning(usr, exp, def);
        insert(meng);

    } else { //old meaning
        if (mengs.size() > 1) throw new MultipleMengsException(mengs);
        else{
            final Meaning meng = mengs.iterator().next();
            meng.addExp(exp);
            meng.setLastPublishedDate(null); //reschedule
        }
    }
    Logger.getAnonymousLogger().log(Level.INFO, "inserted pair <{0},{1}>", new String[]{exp.getExpression(), def.getExpression()});
}

public boolean insert(final MemoEntity entity) {
        if (em.find(entity.getClass(), entity.getId()) == null) {
            et.begin();
            em.persist(entity);
            et.commit();
            return true;
        }
        return false;
    }

public <MemoEntity> MemoEntity get(final Class<MemoEntity> entityClass, final Object primaryKey) {
        return em.find(entityClass, primaryKey);
    }

【问题讨论】:

  • int hash = 7; hash = 67 * hash ???为什么不int hash = 469 + obj.Id?还是只是return obj.Id?不需要向哈希码添加常量,
  • @NullUserException: return 4; 是正确的变体!

标签: java collections object jpa-2.0


【解决方案1】:

实体的 HashCode 和 equals 最好不要使用代理 ID 进行比较,而是使用对象的业务属性或仅使用自然键来实现比较。有关详细信息,请参阅此SO question

编辑:至于为什么这不起作用,我唯一的猜测是您在将对象添加到 HashSet 后正在修改它,特别是更改 ID。这将导致 contains 方法失败,因为它使用 hashCode 作为 hashmap 中的键来定位对象,如果 ID 已更改,它将在底层 hashMap 中查找错误的位置。

【讨论】:

  • 你没有回答问题。
  • @simpatico - 我刚刚开始研究它 - 检查 JDK 源代码。请看我的编辑。
  • 我看不出更改对象(哈希码或 objId 除外)是如何导致问题的。我会发布测试代码。
  • 我说更改 objid 会导致问题。
  • 等等,我猜你闻到了正确的地方。 ObjId 是:@Id @GeneratedValue(strategy = GenerationType.AUTO) 我无法控制。你说可能是 JPA 改变了它(比如先把它设为 0,然后在持久化时设置它,然后缓存中的对象将没有正确的哈希码?)但是如果使用 equals 则只有 objIds 重要。跨度>
【解决方案2】:

如果

assertTrue(def.getMengs().containsAll(exp.getMengs()));

通过,那么最可能的解释是def.getMengs() 包含来自exp.getMengs() 的所有元素加上其他一些不在后者中的元素。

尝试反转它,即

assertTrue(exp.getMengs().containsAll(def.getMengs()));

或者干脆

assertEqual(exp.getMengs().size(), def.getMengs().size());

编辑

我知道我误读了您的问题。但是,这确实澄清了情况。 equals 方法检查 3 件事。 1) 两者都是Set 类型。 2) 相同大小和 3) “a”包含“b”中的所有元素。

你似乎没有通过最后一个。事实上,由于在 HashSet 上对自身执行 containsAll 是失败的,它必须是含义上的 equals 方法。阅读 Sets 上 containsAllcontains 方法的代码 (Java 6) 清楚地表明 hashCode 方法不用于此目的。

【讨论】:

  • 不,它没有。调试器显示它们具有相同的大小,并且确实大小断言通过了。
  • 有趣的是这也失败了:assertTrue(def.getMengs().containsAll(def.getMengs()));
【解决方案3】:

您的 objId 是整数还是长整数?那么您的问题可能与equals方法中的自动装箱有关:

objId == other.objId

这将适用于您的第一个测试中的小常量,因为这些是缓存的。在这种情况下,通常您应该使用 equals 方法。 equals 方法中的那一行最好写成:

 return objId == null ? this == other : objId.equals(other.objId);

【讨论】:

    【解决方案4】:

    你正在使用休眠实体,所以你不应该直接引用成员变量,因为它们可能是从数据库中延迟加载的。所以,你的 equals/hashCode 方法应该使用 getObjId() 而不是 objId。

    另外,如另一篇文章所述,如果您的 objId 是对象类型(整数、长整数),则不应使用“==”。

    【讨论】:

      【解决方案5】:

      如果您按 objId 散列,那么它不应该是可变的(正如 setObjId 方法所建议的那样)。特别是,如果您坚持使用该方法,则不应在将对象放入哈希集中后调用它。

      【讨论】:

      • hmm..我添加了 setObjId 方法只是为了测试,现在我遇到了问题。我同意它不应该是可变的。但是,您根本没有回答问题,是吗?
      • 如果您在将 objId 放入 HashSet 后更改了它(我不知道您是否这样做,因为您没有包含所有代码),那么 这就是问题所在(所以我正在有条件地回答这个问题:))。
      • 我从不更改 objId,除非在那个测试代码中(这不是问题的一部分)。
      • 那么问题可能出在您从问题中省略的某些代码中。顺便说一句,如果“其他”是“意义”的一个实例,那么它不是“空”,所以你有一个额外的检查。
      【解决方案6】:

      问题在于 containsAll 比较对对象的引用(使用 ==)而不是调用 .equals。 HashSet.equals 根据文档在内部使用 containsAll。

      一种可能的解决方案是从 HashSet 派生您自己的类并覆盖 containsAll 方法。

      【讨论】:

      • @NullUserException,那么问题出在哪里?你说常量是不必要的,但这不是问题。
      • @NullUserException 你确定 containsAll 在所有实现中都使用 equals() 吗?我在任何地方都找不到,如果你能发布一个链接,那就太好了。
      猜你喜欢
      • 1970-01-01
      • 2017-02-08
      • 2021-01-30
      • 2015-03-14
      • 2014-11-03
      • 1970-01-01
      • 1970-01-01
      • 2022-06-19
      相关资源
      最近更新 更多