Java Hashset.contains() 产生神秘的结果

Posted

技术标签:

【中文标题】Java Hashset.contains() 产生神秘的结果【英文标题】:Java Hashset.contains() produces mysterious result 【发布时间】:2011-05-06 04:24:56 【问题描述】:

我通常不使用 Java 编写代码,但最近我开始别无选择。我可能对如何正确使用 HashSet 有一些重大误解。因此,我所做的事情可能完全是错误的。但是,我很感谢您提供的任何帮助。所以实际的问题:

在我编写的一个小程序中,我生成了非常相似的对象,这些对象在创建时将具有非常特定的 id(string 或在我的上一次迭代中为 long)。因为每个对象都会产生新对象,所以我想过滤掉所有我已经创建的对象。因此,我开始将每个新对象的 id 放入我的 Hash(Set) 并使用HashSet.contains() 进行测试,如果之前创建了一个对象。完整代码如下:

// hashtest.java
import java.util.HashSet;

class L 
    public long l;
    public L(long l) 
        this.l = l;
    
    public int hashCode() 
        return (int)this.l;
    
    public boolean equals(L other) 
        return (int)this.l == (int)other.l;
    


class hashtest 
    public static void main(String args[]) 
        HashSet<L> hash = new HashSet<L>();
        L a = new L(2);
        L b = new L(2);
        hash.add(a);
        System.out.println(hash.contains(a));
        System.out.println(hash.contains(b));
        System.out.println(a.equals(b));
        System.out.println(a.hashCode() == b.hashCode());
    

产生以下输出:

true
false
true
true    

很明显,contains 没有使用L 提供的equals 功能,或者我对这个概念有一些重大误解......

我用openjdk(ubuntu中包含的当前版本)和Win7上的Oracle官方当前java对其进行了测试

HashSet.contains() 的完整官方 java-api 文档:

public boolean contains(Object o)

如果此集合包含 指定的元素。更正式地说, 返回true 当且仅当此集合 包含一个元素 e 使得 (o==null ? e==null : o.equals(e)).

http://download.oracle.com/javase/6/docs/api/java/util/HashSet.html#contains(java.lang.Object)

有什么想法或建议吗?

【问题讨论】:

【参考方案1】:

当您将对象添加到集合时,它会在内部调用 equalshashCode 方法。您必须覆盖这两种方法。例如,我使用了nameiddesignation 的一个 bean 类,然后创建并添加了一个 employee 对象。

HashSet<Employee> set = new HashSet<Employee>();
Employee employee = new Employee();
employee.setId(1);
employee.setName("jagadeesh");
employee.setDesignation("programmer");
set.add(employee);
Employee employee2 = new Employee();
employee2.setId(1);
employee2.setName("jagadeesh");
employee2.setDesignation("programmer");
set.add(employee2);

set.add() 在内部调用 equalshashCode 方法。所以你必须在你的 bean 类中重写这两个方法。

@Override
public int hashCode()
    StringBuffer buffer = new StringBuffer();
    buffer.append(this.name);
    buffer.append(this.id);
    buffer.append(this.designation);
    return buffer.toString().hashCode();

@Override
public boolean equals(Object object)
    if (object == null) return false;
    if (object == this) return true;
    if (this.getClass() != object.getClass())return false;
    Employee employee = (Employee)object;
    if(this.hashCode()== employee.hashCode())return true;
   return false;
   

这里我们覆盖了equals()hashCode()。当您将对象添加到HashSet 方法时,它会在内部迭代所有对象并调用equals 方法。因此我们覆盖hashCode,它将每个对象hashCode与其当前hashCode进行比较,如果两者相等则返回true,否则返回false。

【讨论】:

【参考方案2】:

你实际上并没有覆盖Object.equals;相反,您正在定义一个名称相同但参数不同的新方法。请注意,Object.equals 采用 Object 参数,而您的 equals 方法采用 L 参数。如果您重写您的 equals 方法以获取 Object 并在运行时对 L 执行必要的类型检查/强制转换,那么您的代码将按预期工作。

另外,这就是为什么当你的 JRE 支持 @Override 注释时你真的应该使用它们。这样,如果您在打算覆盖现有方法时意外实现了新方法,编译器会报错。

举个例子,这个 equals 方法应该可以正常工作。 (另外,如果要比较的对象为空,它不会失败。)

@Override
public boolean equals(Object other) 
    return other != null && other instanceof L && this.l == ((L)other).l;

【讨论】:

哈哈,我确实做到了。我的心理编译器今天一定不能工作。 :)【参考方案3】:

您的equals 方法需要采用Object。 因为您将其声明为采用 L,所以它变成了额外的重载,而不是覆盖该方法。 因此,当hashSet 类调用equals 时,它解析为基类Object.equals 方法。当您调用equals 时,您调用了您的重载,因为ab声明L 而不是Object

为防止将来出现此问题,您应在覆盖方法时添加@Override。 这样,如果它实际上不是覆盖,编译器会警告你。

【讨论】:

澄清一下——如果你没有正确的“equals”版本的签名,那么你实际上并没有重载equals,HashSet(和其他所有东西)将使用原始的equals方法,除非它直接知道 L 类(就像您的测试代码一样)。要对此进行测试,请添加: System.out.println(a.equals((Object) b));到你的测试程序——它应该返回 false。

以上是关于Java Hashset.contains() 产生神秘的结果的主要内容,如果未能解决你的问题,请参考以下文章

HashSet contains() 方法

如何在 java hashset 中查找并返回对象

哈希表

利用 HashSet 去过滤元素是否重复

java 152.最大产品子阵列(第1个).java

java 152.最大产品子阵列(第1个).java