hashcode()和equals()及HashSet判断对象相等。
1. 首先equals()和hashcode()这两个方法都是从object类中继承过来的。
public boolean equals(Object obj)
指示某个其他对象是否与此对象“相等”。
equals 方法在非空对象引用上实现相等关系:
* 自反性:对于任何非空引用值 x,x.equals(x) 都应返回 true。
* 对称性:对于任何非空引用值 x 和 y,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应返回 true。
* 传递性:对于任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true,
并且 y.equals(z) 返回 true,那么 x.equals(z) 应返回 true。
* 一致性:对于任何非空引用值 x 和 y,多次调用 x.equals(y) 始终返回 true 或始终返回 false,
前提是对象上 equals 比较中所用的信息没有被修改。
* 对于任何非空引用值 x,x.equals(null) 都应返回 false。
注意:以上这五点是重写equals()方法时, 必须遵守的准则,如果违反会出现意想不到的结果
Object 类的 equals 方法实现的是对象上差别可能性最大的相等关系:
即,对于任何非空引用值 x 和 y,当且仅当 x 和 y 引用同一个对象时,
此方法才返回 true(x == y 具有值 true)。
也就是说equals()方法在object类中定义如下:
public boolean equals(Object obj) {
return (this == obj);
}
对两个对象的地址值进行的比较(即比较引用是否相同)。
注意1:当此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,
该协定声明 相等对象必须具有相等的哈希码。
但是我们必需清楚,当String 、Math、还有Integer、Double。。。。等这些封装类在使用equals()方法时,
已经覆盖了object类的equals()方法。比如在String类中如下:
public boolean equals (Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = count;
if (n == anotherString.count) {
char v1[] = value;
char v2[] = anotherString.value;
int i = offset;
int j = anotherString.offset;
while (n-- != 0) {
if (v1[i++] != v2[j++])
return false;
}
return true;
}
}
return false;
}
很明显, 这是进行的内容比较,而已经不再是地址的比较 。依次类推Double、Integer、Math等等这些类都是重写了equals()方法的,
从而进行的是内容的比较。当然了基本类型是进行值的比较,这个没有什么好说的。
public int hashCode()
返回该对象的哈希码值。支持该方法是为哈希表提供一些优点,例如,java.util.Hashtable 提供的哈希表。
hashCode 的常规协定是:
* 在 Java 应用程序执行期间,在同一对象上多次调用 hashCode 方法时,必须一致地返回相同的整数,
前提是对象上 equals 比较中所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,
该整数无需保持一致。
* 如果根据 equals(Object) 方法,两个对象是相等的,那么在两个对象中的每个对象上调用 hashCode 方法都必须生成相同的整数结果。
* 以下情况 不是必需的: 如果根据 equals(java.lang.Object) 方法,两个对象不相等,
那么在两个对象中的任一对象上调用 hashCode 方法必定会生成不同的整数结果。
但是,程序员应该知道,为不相等的对象生成不同整数结果可以提高哈希表的性能。
实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。
(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是 JavaTM 编程语言不需要这种实现技巧。) 。
当然我们可以在自己写的类中覆盖hashcode()方法,
比如String、 Integer、Double。。。。等等这些类都是覆盖了hashcode()方法的。
例如在String类中定义的hashcode()方法如下:
public int hashCode () {
int h = hash;
if (h == 0) {
int off = offset;
char val[] = value;
int len = count;
for (int i = 0; i < len; i++) {
h = 31*h + val[off++];
}
hash = h;
}
return h;
}
解释一下这个程序(String的API中写到):
s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
使用 int 算法,这里 s 是字符串的第 i 个字符,n 是字符串的长度,^ 表示求幂。(空字符串的哈希码为 0。)
这里我们根据hashCode的常规协定首先要明白一个问题:
一, equals()相等的两个对象,hashcode()一定相等.
二, equals()不相等的两个对象,却并不能证明他们的hashcode()不相等。
换句话说,equals()方法不相等的两个对象,hashcode()有可能相等。(我的理解是由于哈希码在生成的时候产生冲突造成的)。
三,反过来: hashcode()不等,一定能推出equals()也不等(因为如果equals()相等的话就和第一条矛盾了);
hashcode()相等,equals()可能相等,也可能不等。
2.hashset是根据什么原理来存取对象的呢?
在hashset中不允许出现重复对象,元素的位置也是不确定的。
在hashset中判断 两个对象是否重复的规则是:
1), 判断两个对象的hashCode是否相等.
1. 首先equals()和hashcode()这两个方法都是从object类中继承过来的。
public boolean equals(Object obj)
指示某个其他对象是否与此对象“相等”。
equals 方法在非空对象引用上实现相等关系:
* 自反性:对于任何非空引用值 x,x.equals(x) 都应返回 true。
* 对称性:对于任何非空引用值 x 和 y,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应返回 true。
* 传递性:对于任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true,
并且 y.equals(z) 返回 true,那么 x.equals(z) 应返回 true。
* 一致性:对于任何非空引用值 x 和 y,多次调用 x.equals(y) 始终返回 true 或始终返回 false,
前提是对象上 equals 比较中所用的信息没有被修改。
* 对于任何非空引用值 x,x.equals(null) 都应返回 false。
注意:以上这五点是重写equals()方法时, 必须遵守的准则,如果违反会出现意想不到的结果
Object 类的 equals 方法实现的是对象上差别可能性最大的相等关系:
即,对于任何非空引用值 x 和 y,当且仅当 x 和 y 引用同一个对象时,
此方法才返回 true(x == y 具有值 true)。
也就是说equals()方法在object类中定义如下:
public boolean equals(Object obj) {
return (this == obj);
}
对两个对象的地址值进行的比较(即比较引用是否相同)。
注意1:当此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,
该协定声明 相等对象必须具有相等的哈希码。
但是我们必需清楚,当String 、Math、还有Integer、Double。。。。等这些封装类在使用equals()方法时,
已经覆盖了object类的equals()方法。比如在String类中如下:
public boolean equals (Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = count;
if (n == anotherString.count) {
char v1[] = value;
char v2[] = anotherString.value;
int i = offset;
int j = anotherString.offset;
while (n-- != 0) {
if (v1[i++] != v2[j++])
return false;
}
return true;
}
}
return false;
}
很明显, 这是进行的内容比较,而已经不再是地址的比较 。依次类推Double、Integer、Math等等这些类都是重写了equals()方法的,
从而进行的是内容的比较。当然了基本类型是进行值的比较,这个没有什么好说的。
public int hashCode()
返回该对象的哈希码值。支持该方法是为哈希表提供一些优点,例如,java.util.Hashtable 提供的哈希表。
hashCode 的常规协定是:
* 在 Java 应用程序执行期间,在同一对象上多次调用 hashCode 方法时,必须一致地返回相同的整数,
前提是对象上 equals 比较中所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,
该整数无需保持一致。
* 如果根据 equals(Object) 方法,两个对象是相等的,那么在两个对象中的每个对象上调用 hashCode 方法都必须生成相同的整数结果。
* 以下情况 不是必需的: 如果根据 equals(java.lang.Object) 方法,两个对象不相等,
那么在两个对象中的任一对象上调用 hashCode 方法必定会生成不同的整数结果。
但是,程序员应该知道,为不相等的对象生成不同整数结果可以提高哈希表的性能。
实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。
(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是 JavaTM 编程语言不需要这种实现技巧。) 。
当然我们可以在自己写的类中覆盖hashcode()方法,
比如String、 Integer、Double。。。。等等这些类都是覆盖了hashcode()方法的。
例如在String类中定义的hashcode()方法如下:
public int hashCode () {
int h = hash;
if (h == 0) {
int off = offset;
char val[] = value;
int len = count;
for (int i = 0; i < len; i++) {
h = 31*h + val[off++];
}
hash = h;
}
return h;
}
解释一下这个程序(String的API中写到):
s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
使用 int 算法,这里 s 是字符串的第 i 个字符,n 是字符串的长度,^ 表示求幂。(空字符串的哈希码为 0。)
这里我们根据hashCode的常规协定首先要明白一个问题:
一, equals()相等的两个对象,hashcode()一定相等.
二, equals()不相等的两个对象,却并不能证明他们的hashcode()不相等。
换句话说,equals()方法不相等的两个对象,hashcode()有可能相等。(我的理解是由于哈希码在生成的时候产生冲突造成的)。
三,反过来: hashcode()不等,一定能推出equals()也不等(因为如果equals()相等的话就和第一条矛盾了);
hashcode()相等,equals()可能相等,也可能不等。
2.hashset是根据什么原理来存取对象的呢?
在hashset中不允许出现重复对象,元素的位置也是不确定的。
在hashset中判断 两个对象是否重复的规则是:
1), 判断两个对象的hashCode是否相等.
如果不相等,认为两个对象也不相等,完毕; 如果相等,转入2)
(这一点只是为了提高存储效率而要求的,其实理论上没有也可以,但如果没有,实际使用时效率会大大降低,
所以我们这里将其做为必需的。后面会重点讲到这个问题。)
2), 判断两个对象用equals运算是否相等
如果不相等,认为两个对象也不相等; 如果相等,认为两个对象相等(equals()是判断两个对象是否相等的关键)
为什么是两条准则,难道用第一条不行吗? 不行,因为前面已经说了,hashcode()相等时,equals()方法也可能不等,
所以必须用第2条准则进行限制,才能保证加入的为非重复元素 。
那么为什么不直接用equals()方法判断呢?
我想是因为效率的原因。很容易让hashcode()的效率比equals()高些,
因为它不必每次都重新算hash值,具体参照上面String的hashcode()实现
例1:
public static void main (String args[]){
String s1=new String("zhaoxudong");
String s2=new String("zhaoxudong");
System.out.println(s1==s2); //false
System.out.println(s1.equals(s2)); //true
System.out.println(s1.hashCode()); //s1.hashcode()等于s2.hashcode()
System.out.println(s2.hashCode());
Set hashset=new HashSet();
hashset.add(s1);
hashset.add(s2);
Iterator it=hashset.iterator();
while (it.hasNext())
{
System.out.println(it.next());
}
最后在while循环的时候只打印出了一个”zhaoxudong”。
输出结果为:
(这一点只是为了提高存储效率而要求的,其实理论上没有也可以,但如果没有,实际使用时效率会大大降低,
所以我们这里将其做为必需的。后面会重点讲到这个问题。)
2), 判断两个对象用equals运算是否相等
如果不相等,认为两个对象也不相等; 如果相等,认为两个对象相等(equals()是判断两个对象是否相等的关键)
为什么是两条准则,难道用第一条不行吗? 不行,因为前面已经说了,hashcode()相等时,equals()方法也可能不等,
所以必须用第2条准则进行限制,才能保证加入的为非重复元素 。
那么为什么不直接用equals()方法判断呢?
我想是因为效率的原因。很容易让hashcode()的效率比equals()高些,
因为它不必每次都重新算hash值,具体参照上面String的hashcode()实现
例1:
public static void main (String args[]){
String s1=new String("zhaoxudong");
String s2=new String("zhaoxudong");
System.out.println(s1==s2); //false
System.out.println(s1.equals(s2)); //true
System.out.println(s1.hashCode()); //s1.hashcode()等于s2.hashcode()
System.out.println(s2.hashCode());
Set hashset=new HashSet();
hashset.add(s1);
hashset.add(s2);
Iterator it=hashset.iterator();
while (it.hasNext())
{
System.out.println(it.next());
}
最后在while循环的时候只打印出了一个”zhaoxudong”。
输出结果为:
false
true
-967303459
-967303459
true
-967303459
-967303459
zhaoxudong
这是因为String类已经重写了equals()方法和hashcode()方法,所以在根据上面的第1.2条原则判定时,
hashset认为它们是相等的对象,是重复添加。
例2:
import java.util.*;
public class HashSetTest
{
public static void main(String[] args)
{
HashSet hs=new HashSet();
hs.add(new Student(1,"zhangsan"));
hs.add(new Student(2,"lisi"));
hs.add(new Student(3,"wangwu"));
hs.add(new Student(1,"zhangsan"));
Iterator it=hs.iterator();
while (it.hasNext())
{
System.out.println(it.next());
}
}
}
class Student
{
int num;
String name;
Student(int num,String name)
{
this.num=num;
this.name=name;
}
public String toString()
{
return num+":"+name;
}
}
输出结果为:
1:zhangsan
1:zhangsan
3:wangwu
2:lisi
问题出现了:为什么hashset添加了相等的元素呢,这是不是和hashset的原则违背了呢?回答是:没有
因为在根据hashcode()对两次建立的new Student(1,"zhangsan")对象进行比较时,生成的是不同的哈希码值,
所以hashset把他当作不同的对象对待了,当然此时的 equals()方法返回的值也不等(这个不用解释了吧)。
那么为什么会生成不同的哈希码值呢?原因就在于我们自己写的Student类并没有重写自己的hashcode()和equals()方法,
所以在比较时,是继承的object类中的 hashcode()方法,它返回的是和对象地址(引用地址)对应的数,
使用new方法创建对象,两次生成的当然是不同的对象了,造成的结果就是两个对象的hashcode()返回的值不一样。
所以根据第一个准则,hashset会把它们当作不同的对象对待,自然也用不着第二个准则进行判定了。
那么怎么解决这个问题呢?
答案是:在Student类中重新hashcode()和equals()方法。
例3:
class Student
{
int num;
String name;
Student (int num,String name)
{
this.num=num;
this.name=name;
}
public int hashCode()
{
return num*name.hashCode();
}
public boolean equals(Object o)
{
Student s=(Student)o;
return num==s.num && name.equals(s.name);
}
public String toString()
{
return num+":"+name;
}
}
根据重写的方法,即便两次调用了new Student(1,"zhangsan"),我们在获得对象的哈希码时,获得的哈希码肯定是一样的。
当然根据equals()方法我们也可判断是相同的。所以在向hashset集合中添加时把它们当作重复元素看待了。
所以运行修改后的程序时,我们会发现运行结果是:
1:zhangsan
3:wangwu
2:lisi
可以看到重复元素的问题已经消除。
例4:
import java.util.HashSet;
import java.util.Random;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
public class Test {
/**
* @param args
*/
public static void main (String[] args) {
HashSet<People> set=new HashSet();
set.add(new People("robin",1,21));
set.add(new People("hb",2,20));
set.add(new People("harry",3,30));
set.add(null);
People p4=new People("robin",4,25);
set.add(p4);
set.add(new People("yp",5,28));
for(People p:set)
System.out.println(p);
}
}
class People {
String name;
int id;
int age;
public People(String name,int id)
{
this(name,id,0);
}
public People(String name,int id,int age)
{
this.name=name;
this.id=id;
this.age=age;
}
public String toString()
{
return id+name+age;
}
public boolean equals(Object o)
{
if(o==null)
return false;
if(!(o instanceof People))
return false;
People p=(People)o;
boolean res=name.equals(p.name);
if(res)
System.out.println("name "+name+" is double");
else
System.out.println(name+" vS "+p.name);
return res;
}
public int hashCode()
{
return name.hashCode();
}
}
结果:
这是因为String类已经重写了equals()方法和hashcode()方法,所以在根据上面的第1.2条原则判定时,
hashset认为它们是相等的对象,是重复添加。
例2:
import java.util.*;
public class HashSetTest
{
public static void main(String[] args)
{
HashSet hs=new HashSet();
hs.add(new Student(1,"zhangsan"));
hs.add(new Student(2,"lisi"));
hs.add(new Student(3,"wangwu"));
hs.add(new Student(1,"zhangsan"));
Iterator it=hs.iterator();
while (it.hasNext())
{
System.out.println(it.next());
}
}
}
class Student
{
int num;
String name;
Student(int num,String name)
{
this.num=num;
this.name=name;
}
public String toString()
{
return num+":"+name;
}
}
输出结果为:
1:zhangsan
1:zhangsan
3:wangwu
2:lisi
问题出现了:为什么hashset添加了相等的元素呢,这是不是和hashset的原则违背了呢?回答是:没有
因为在根据hashcode()对两次建立的new Student(1,"zhangsan")对象进行比较时,生成的是不同的哈希码值,
所以hashset把他当作不同的对象对待了,当然此时的 equals()方法返回的值也不等(这个不用解释了吧)。
那么为什么会生成不同的哈希码值呢?原因就在于我们自己写的Student类并没有重写自己的hashcode()和equals()方法,
所以在比较时,是继承的object类中的 hashcode()方法,它返回的是和对象地址(引用地址)对应的数,
使用new方法创建对象,两次生成的当然是不同的对象了,造成的结果就是两个对象的hashcode()返回的值不一样。
所以根据第一个准则,hashset会把它们当作不同的对象对待,自然也用不着第二个准则进行判定了。
那么怎么解决这个问题呢?
答案是:在Student类中重新hashcode()和equals()方法。
例3:
class Student
{
int num;
String name;
Student (int num,String name)
{
this.num=num;
this.name=name;
}
public int hashCode()
{
return num*name.hashCode();
}
public boolean equals(Object o)
{
Student s=(Student)o;
return num==s.num && name.equals(s.name);
}
public String toString()
{
return num+":"+name;
}
}
根据重写的方法,即便两次调用了new Student(1,"zhangsan"),我们在获得对象的哈希码时,获得的哈希码肯定是一样的。
当然根据equals()方法我们也可判断是相同的。所以在向hashset集合中添加时把它们当作重复元素看待了。
所以运行修改后的程序时,我们会发现运行结果是:
1:zhangsan
3:wangwu
2:lisi
可以看到重复元素的问题已经消除。
例4:
import java.util.HashSet;
import java.util.Random;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
public class Test {
/**
* @param args
*/
public static void main (String[] args) {
HashSet<People> set=new HashSet();
set.add(new People("robin",1,21));
set.add(new People("hb",2,20));
set.add(new People("harry",3,30));
set.add(null);
People p4=new People("robin",4,25);
set.add(p4);
set.add(new People("yp",5,28));
for(People p:set)
System.out.println(p);
}
}
class People {
String name;
int id;
int age;
public People(String name,int id)
{
this(name,id,0);
}
public People(String name,int id,int age)
{
this.name=name;
this.id=id;
this.age=age;
}
public String toString()
{
return id+name+age;
}
public boolean equals(Object o)
{
if(o==null)
return false;
if(!(o instanceof People))
return false;
People p=(People)o;
boolean res=name.equals(p.name);
if(res)
System.out.println("name "+name+" is double");
else
System.out.println(name+" vS "+p.name);
return res;
}
public int hashCode()
{
return name.hashCode();
}
}
结果:
name robin is double
null
1robin21
3harry30
5yp28
2hb20
注意1:当试图添加一个重复元素到hashset时,新元素并不会把旧元素替换掉,
而只是新元素不会添加到hashset(不会抛异常。)
null
1robin21
3harry30
5yp28
2hb20
注意1:当试图添加一个重复元素到hashset时,新元素并不会把旧元素替换掉,
而只是新元素不会添加到hashset(不会抛异常。)