重新编写equals()方法,hashCode()方法,以及toString(),提供自定义的相等标准,以及自描述方法
下面给出一个实例,重新编写equals()方法,提供自定义的相等标准
1 public class PersonTest { 2 public static void main(String[] args) { 3 Person p1 = new Person("孙悟空", "1234"); 4 Person p2 = new Person("孙行者", "1234"); 5 Person p3 = new Person("孙大圣", "12345"); 6 System.out.println("p1和p2是否相等?" + p1.equals(p2)); 7 System.out.println("p1和p3是否相等?" + p1.equals(p3)); 8 System.out.println("p2和p3是否相等?" + p2.equals(p3)); 9 } 10 } 11 12 class Person { 13 private String name; 14 private String id; 15 16 public Person() { 17 } 18 public Person(String name, String id) { 19 this.name = name; 20 this.id = id; 21 } 22 23 public String getId() { 24 return this.id; 25 } 26 public boolean equals(Object obj) { 27 //如果两个对象为同一个对象 28 if( this == obj) { 29 return true; 30 } 31 //当obj不为null,其它是Person类的实例时 32 if( obj != null && obj.getClass() == Person.class) { 33 Person obj2 = (Person)obj; 34 //并且当前对象的id与obj对象的id相等才可判断两个对象相等 35 if (this.getId().equals(obj2.getId())) { 36 return true; 37 } 38 } 39 return false; 40 } 41 }
上述实例运行结果显示:
p1和p2是否相等?true
p1和p3是否相等?false
p2和p3是否相等?false
通常而言,正确地重写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。
- 对任何不是null的x,x.equals(null)一定返回false。
特别注意:
重新equals()方法后,一定要重写hashCode()方法,否则会引起一些意想不到的错误。
所以,可以为上面的代码添加如下的hashCode()方法。
1 public int hashCode() { 2 final int PRIME = 31; 3 int result = 1; 4 result = PRIME * result + getId().hashCode(); 5 return result; 6 }
注意:不同类型hashCode值的计算可以采用如下公式。
Field类型 | 计算公式 |
boolean | hashCode=(f?0:1); |
整数类型(byte,short,char,int) |
hashCode=(int)f; |
long | hashCode=(int)(f^(f>>>32)); |
float | hashCode=Float.floatToIntBits(f); |
double |
long l=Double.doubleToLongBits(f); hashCode=(int)(l^(l>>>32); |
普通引用类型 | hashCode=f.hashCode(); |
使用系数为31的原因如下:
- 31是一个素数,素数作用就是如果我用一个数字来乘以这个素数,那么最终的出来的结果只能被素数本身和被乘数还有1来整除!。(减少冲突)
- 31可以 由i*31== (i<<5)-1来表示,现在很多虚拟机里面都有做相关优化.(提高算法效率)
- 选择系数的时候要选择尽量大的系数。因为如果计算出来的hash地址越大,所谓的“冲突”就越少,查找起来效率也会提高。(减少冲突)
- 并且31只占用5bits,相乘造成数据溢出的概率较小。
实例:
Java中的集合有两类,一类是List,一类是Set。List内的元素是有序的,元素可以重复。Set元素无序,但元素不可重复。
下面,通过一个实例来加深对equals和hashCode方法的理解。
1 import java.util.HashSet; 2 3 public class HashSetAndHashCodeTest { 4 public static void main(String[] args) { 5 HashSet<Point1> hs1 = new HashSet<Point1>(); 6 Point1 p11 = new Point1(3, 3); 7 Point1 p12 = new Point1(3, 3); 8 Point1 p13 = new Point1(3, 5); 9 hs1.add(p11); 10 hs1.add(p11); 11 hs1.add(p12); 12 hs1.add(p13); 13 System.out.println(hs1.size()); //答案是3 14 15 16 HashSet<Point2> hs2 = new HashSet<Point2>(); 17 Point2 p21 = new Point2(3, 3); 18 Point2 p22 = new Point2(3, 3); 19 Point2 p23 = new Point2(3, 5); 20 hs2.add(p21); 21 hs2.add(p22); 22 hs2.add(p23); 23 System.out.println(hs2.size()); // 答案是2。p21和p22被认为是同一个对象。 24 25 26 HashSet<Point3> hs3 = new HashSet<Point3>(); 27 Point3 p31 = new Point3(3, 3); 28 Point3 p32 = new Point3(3, 3); 29 Point3 p33 = new Point3(3, 5); 30 hs3.add(p31); 31 hs3.add(p32); 32 hs3.add(p33); 33 System.out.println(hs3.size()); // 可能是2,可能是3。因为根据内存地址算出的hashcode不知道是否在一个区域。 34 } 35 } 36 37 /** 38 * 1 没有重写hashCode和equals的方法 39 */ 40 class Point1 { 41 private int x; 42 private int y; 43 44 public Point1(int x, int y) { 45 super(); 46 this.x = x; 47 this.y = y; 48 } 49 50 public int getX() { 51 return x; 52 } 53 54 public void setX(int x) { 55 this.x = x; 56 } 57 58 public int getY() { 59 return y; 60 } 61 62 public void setY(int y) { 63 this.y = y; 64 } 65 } 66 67 /** 68 * 2 重写hashCode和equals的方法 * 69 */ 70 class Point2 { 71 private int x; 72 private int y; 73 74 Point2(int x, int y) { 75 super(); 76 this.x = x; 77 this.y = y; 78 } 79 80 @Override 81 public int hashCode() { 82 final int prime = 31; 83 int result = 1; 84 result = prime * result + x; 85 result = prime * result + y; 86 return result; 87 } 88 89 @Override 90 public boolean equals(Object obj) { 91 if (this == obj) return true; 92 if (obj == null) return false; 93 if (getClass() != obj.getClass()) return false; 94 Point2 other = (Point2) obj; 95 if (x != other.x) return false; 96 if (y != other.y) return false; 97 return true; 98 } 99 100 public int getX() { 101 return x; 102 } 103 104 public void setX(int x) { 105 this.x = x; 106 } 107 108 public int getY() { 109 return y; 110 } 111 112 public void setY(int y) { 113 this.y = y; 114 } 115 } 116 117 118 /** 119 * 3 没有重写hashCode的方法,但重写equals的方法 120 */ 121 class Point3 { 122 private int x; 123 private int y; 124 125 Point3(int x, int y) { 126 super(); 127 this.x = x; 128 this.y = y; 129 } 130 131 @Override 132 public boolean equals(Object obj) { 133 if (this == obj) return true; 134 if (obj == null) return false; 135 if (getClass() != obj.getClass()) return false; 136 Point3 other = (Point3) obj; 137 if (x != other.x) return false; 138 if (y != other.y) return false; 139 return true; 140 } 141 142 public int getX() { 143 return x; 144 } 145 146 public void setX(int x) { 147 this.x = x; 148 } 149 150 public int getY() { 151 return y; 152 } 153 154 public void setY(int y) { 155 this.y = y; 156 } 157 }
对于自描述方法toString(),系统默认的方法如下:
1 public String toString() { 2 return getClass().getName() + "@" + Integer.toHexString(hashCode()); 3 }
这样得到的并非自描述信息,而是实例的内存地址。所以,针对上面的Point1类,可以给出下面的自描述方法:
1 public String toString() { 2 return getClass().getName() + "@[x=" + this.getX() + ", y=" + this.getY() + "]"; 3 }
参考内容:
- http://www.oschina.net/question/82993_75533
- http://www.2cto.com/kf/201211/168714.html
- http://howtodoinjava.com/2012/10/09/working-with-hashcode-and-equals-methods-in-java/