Java中Set的contains()方法 —— hashCode与equals方法的约定及重写原则



翻译人员: 铁锚

翻译时间: 2013年11月5日

原文链接: Java hashCode() and equals() Contract for the contains(Object o) Method of Set



本文主要讨论 集合Set 中存储对象的 hashCode 与 equals 方法应遵循的约束关系.



新手对Set中contains()方法的疑惑

import java.util.HashSet;

class Dog{
	String color;

	public Dog(String s){
		color = s;
	}
}

public class SetAndHashCode {
	public static void main(String[] args) {
		HashSet<Dog> dogSet = new HashSet<Dog>();
		dogSet.add(new Dog("white"));
		dogSet.add(new Dog("white"));

		System.out.println("We have " + dogSet.size() + " white dogs!");

		if(dogSet.contains(new Dog("white"))){
			System.out.println("We have a white dog!");
		}else{
			System.out.println("No white dog!");
		}
	}
}

上述代码的输出为:

We have 2 white dogs!
No white dog!

程序中添加了两只白色的小狗到集合dogSet中. 且 size()方法显示有2只白色的小狗.但为什么用 contains()方法来判断时却提示没有白色的小狗呢?



Set的contains(Object o) 方法详解

Java的API文档指出: 当且仅当 本set包含一个元素 e,并且满足(o==null ? e==null : o.equals(e))条件时,contains()方法才返回true. 因此 contains()方法 必定使用equals方法来检查是否相等.

需要注意的是: set 中是可以包含 null值的(常见的集合类都可以包含null值). 所以如果添加了null,然后判断是否包含null,将会返回true,代码如下所示:

HashSet<Dog> a = new HashSet<Dog>();
a.add(null);
if(a.contains(null)){
	System.out.println("true");
}

Java的根类Object定义了  public boolean equals(Object obj) 方法.因此所有的对象,包括数组(array,[]),都实现了此方法。

在自定义类里,如果没有明确地重写(override)此方法,那么就会使用Object类的默认实现.即只有两个对象(引用)指向同一块内存地址(即同一个实际对象, x==y为true)时,才会返回true。

如果把Dog类修改为如下代码,能实现我们的目标吗?

class Dog{
	String color;

	public Dog(String s){
		color = s;
	}

	//重写equals方法, 最佳实践就是如下这种判断顺序:
	public boolean equals(Object obj) {
		if (!(obj instanceof Dog))
			return false;
		if (obj == this)
			return true;
		return this.color == ((Dog) obj).color;
	}

}

英文答案是: no.



问题的关键在于 Java中hashCode与equals方法的紧密联系. hashCode() 是Object类定义的另一个基础方法.



equals()与hashCode()方法之间的设计实现原则为:

如果两个对象相等(使用equals()方法),那么必须拥有相同的哈希码(使用hashCode()方法).

即使两个对象有相同的哈希值(hash code),他们不一定相等.意思就是: 多个不同的对象,可以返回同一个hash值.



hashCode()的默认实现是为不同的对象返回不同的整数.有一个设计原则是,hashCode对于同一个对象,不管内部怎么改变,应该都返回相同的整数值.

在上面的例子中,因为未定义自己的hashCode()实现,因此默认实现对两个对象返回两个不同的整数,这种情况破坏了约定原则。



解决办法

class Dog{
	String color;

	public Dog(String s){
		color = s;
	}

	//重写equals方法, 最佳实践就是如下这种判断顺序:
	public boolean equals(Object obj) {
		if (!(obj instanceof Dog))
			return false;
		if (obj == this)
			return true;
		return this.color == ((Dog) obj).color;
	}

	public int hashCode(){
		return color.length();//简单原则
	}
}

但是上面的hashCode实现,要求Dog的color是不变的.否则会出现如下的这种困惑:

import java.util.HashSet;
import java.util.Set;

public class TestContains {

	public static final class Person{
		private String name = "";
		public Person(String n) {
			setName(n);
		}
		public String getName() {
			return name;
		}
		public void setName(String name) {
			this.name = (name==null)? "" : name;
		}
		@Override
		public int hashCode() {
			// 请考虑是否值得这么做,因为此时name是会变的.
			return name.length();
			// 推荐让name不可改变
		}
		@Override
		public boolean equals(Object obj) {
			if(!(obj instanceof Person)){
				return false;
			}
			if(obj == this){
				return true;
			}
			return this.name.equals(((Person)obj).name);
		}
	};

	public static void main(String[] args) {
		Set<Person> persons = new HashSet<Person>();
		//
		Person person = new Person("tiemao");
		persons.add(person);
		// 修改name, 则依赖hash的集合可能失去作用
		person.setName("ren");
		// 同一个对象,居然是false,原因是我们重写了hashCode,打破了hashCode不变的基本约定
		boolean has = persons.contains(person);
		int size = persons.size();
		System.out.println("has="+has);	// has=false.
		System.out.println("size="+size);// size=1
	}
}

参考文章: 

http://docs.oracle.com/javase/6/docs/api/java/lang/Object.html



相关阅读

1. Java equals() and hashCode() Contract

2. HashMap vs. TreeMap vs. Hashtable vs. LinkedHashMap

3. Java: Find all callers of a method – get all methods that call a particular method

4. 理解Java机制最受欢迎的8幅图

Java中Set的contains()方法的更多相关文章

  1. java中substring的使用方法

    java中substring的使用方法 str=str.substring(int beginIndex);截取掉str从首字母起长度为beginIndex的字符串,将剩余字符串赋值给str: str ...

  2. [java,2017-05-16] java中清空StringBuffer的方法以及耗费时间比较

    java中清空StringBuffer的方法,我能想到的有4种: 1. buffer.setLength(0);  设置长度为0 2. buffer.delete(0, buffer.length() ...

  3. java中BorderLayout的使用方法

    相关设置: 使用BorderLayout布局上下左右中布局5个按键,单击中间的那个按键时就关闭窗口 代码: /**** *java中BorderLayout的使用方法 * 使用BorderLayout ...

  4. 【Java】Java中常用的String方法

    本文转载于:java中常用的String方法 1 length()字符串的长度 String a = "Hello Word!"; System.out.println(a.len ...

  5. Java中Set的contains()方法——hashCode与equals方法的约定及重写原则

    转自:http://blog.csdn.net/renfufei/article/details/14163329 翻译人员: 铁锚 翻译时间: 2013年11月5日 原文链接: Java hashC ...

  6. java中equals和hashCode方法随笔二

    前几天看了篇关于java中equals和hashCode方法的解析 1.Object类中的equals方法和hashCode方法. Object类中的equals和hashCode方法简单明了,所有的 ...

  7. java中static变量和方法的总结

    转自:http://blog.csdn.net/haobo920/article/details/5921621 java中static变量和方法的总结 java中一切皆是对象 一个类中对象的定义一般 ...

  8. Java中wait和sleep方法的区别

    1.两者的区别 这两个方法来自不同的类分别是Thread和Object 最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法(锁代码块和方法锁). wait ...

  9. java中File的delete()方法删除文件失败的原因

    java中File的delete()方法删除文件失败的原因 学习了:http://hujinfan.iteye.com/blog/1266387 的确是忘记关闭了: 引用原文膜拜一下: 一般来说 ja ...

随机推荐

  1. 使用ajax上传图片,支持图片即时浏览,支持js图片压缩后上传给服务器

    使用ajax上传图片,支持图片即时浏览,支持js图片压缩后上传给服务器 ajax上传主要使用了 var reader = new FileReader() 此方法 js图片压缩主要是利用canvas进 ...

  2. Linux下查看进程打开的文件句柄数

    ---查看系统默认的最大文件句柄数,系统默认是1024 # ulimit -n ----查看当前进程打开了多少句柄数 # lsof -n|awk '{print $2}'|sort|uniq -c|s ...

  3. 含有分类变量(categorical variable)的逻辑回归(logistic regression)中虚拟变量(哑变量,dummy variable)的理解

    版权声明:本文为博主原创文章,博客地址:,欢迎大家相互转载交流. 使用R语言做逻辑回归的时候,当自变量中有分类变量(大于两个)的时候,对于回归模型的结果有一点困惑,搜索相关知识发现不少人也有相同的疑问 ...

  4. gdb不知为何显示2次析构

    gdb不知为何显示2次析构 (金庆的专栏 2016.11) gdb 显示2次 A::~A(): (gdb) bt #0 A::~A (this=0x602010, __in_chrg=<opti ...

  5. Spark Streaming + Kafka整合(Kafka broker版本0.8.2.1+)

    这篇博客是基于Spark Streaming整合Kafka-0.8.2.1官方文档. 本文主要讲解了Spark Streaming如何从Kafka接收数据.Spark Streaming从Kafka接 ...

  6. android 自定义view之侧滑效果

    效果图: 看网上的都是两个view拼接,默认右侧的不显示,水平移动的时候把右侧的view显示出来.但是看最新版QQ上的效果不是这样的,但给人的感觉却很好,所以献丑来一发比较高仿的. 知识点: 1.Vi ...

  7. Android图表库MPAndroidChart(十三)——简约的底部柱状图

    Android图表库MPAndroidChart(十三)--简约的底部柱状图 我们继续上一讲,今天还是说下柱状图,这个图的话应该是用的比较多的,所有拿出来溜溜,先看下效果 我们还是来看下基本实现 一. ...

  8. ejabberd编译更新脚本

    ejabberd编译更新脚本 (金庆的专栏 2016.8) 用rebar编译ejabberd源码,然后复制编译所得beam文件到ejabberd安装目录, 调用ejabberdctl热更新. call ...

  9. 24 服务AIDL

    AIDL远程访问服务的方法 创建一个接口类写上方法 然后修改后缀java为aidl 在服务中创建一个类继承Stub类 在远程访问服务的进程把AIDL文件复制(包名不能改变) XXXX.Stub.asI ...

  10. [Python]print vs sys.stdout.write

    之前只是在项目中看到过,没怎么注意,正好跟对象一起看python学习手册,看到了这个部分于是来研究下. python版本 2.7.x os  win7 print  一般就是执行脚本的时候,把信息直接 ...