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】:当您将对象添加到集合时,它会在内部调用 equals
和 hashCode
方法。您必须覆盖这两种方法。例如,我使用了name
、id
、designation
的一个 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()
在内部调用 equals
和 hashCode
方法。所以你必须在你的 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
时,您调用了您的重载,因为a
和b
都声明为L
而不是Object
。
为防止将来出现此问题,您应在覆盖方法时添加@Override
。
这样,如果它实际上不是覆盖,编译器会警告你。
【讨论】:
澄清一下——如果你没有正确的“equals”版本的签名,那么你实际上并没有重载equals,HashSet(和其他所有东西)将使用原始的equals方法,除非它直接知道 L 类(就像您的测试代码一样)。要对此进行测试,请添加: System.out.println(a.equals((Object) b));到你的测试程序——它应该返回 false。以上是关于Java Hashset.contains() 产生神秘的结果的主要内容,如果未能解决你的问题,请参考以下文章