在java 中Object是一个具体的类,但是他的设计主要是为了拓展。当我们写一个类的时候,都会对Java.lang.Object类的一些重要方法进行重写( 改写 override),这些方法包含:hashCode(),toString(),equals(),finalize(),clone(),wait(),notify()/notifyAll() 这八个方法。
这里将总结介绍这些基本的方法,并对这些方法的改写提供总结。
1 equals方法改写
java中==和eqauls()的区别
==是运算符,用于比较两个变量是否相等,而equals是Object类的方法,用于比较两个对象是否相等。默认Object类的equals方法是比较两个对象的地址,此时和==的结果一样。换句话说:基本类型比较用==,比较的是他们的值。默认下,对象用==比较时,比较的是内存地址,如果需要比较对象内容,需要重写equal方法。
下面来理解一下:如果需要比较对象内容,需要重写equal方法。


可以看到Object的equals方法实现,默认Object类的equals方法是比较两个对象的地址,此时和==的结果一样。
String 进行了重写从而实现了字符串的比较。那么下方这里字符串比较肯定返回true了。
public static void main(String[] args) {
String str1 = new String("123");
String str2 = new String("123");
System.out.println(str1.equals(str2)); //true
}
实现高质量的equals方法的诀窍包括
- 使用==操作符检查“参数是否为这个对象的引用”;
- 使用instanceof操作符检查“参数是否为正确的类型”;
- 对于类中的关键属性,检查参数传入对象的属性是否与之相匹配;
- 编写完equals方法后,问自己它是否满足对称性、传递性、一致性;
- 重写equals时总是要重写hashCode;
- 不要将equals方法参数中的Object对象替换为其他的类型,在重写时不要忘掉@Override注解。
理解这几个步骤,直接参考源代码Set,list和map的父类的源代码的equals方法的比较:



重写equals 应该遵守的约定
自反性(x.equals(x)必须返回true);
对称性(x.equals(y)返回true时,y.equals(x)也必须返回true);
传递性(x.equals(y)和y.equals(z)都返回true时,x.equals(z)也必须返回true);
一致性(当x和y引用的对象信息没有被修改时,多次调用x.equals(y)应该得到同样的返回值);
非空性(对于任何非null值的引用x,x.equals(null)必须返回false)。
2 hashCode方法改写
此方法返回对象的哈希码值,什么是哈希码?
哈希码产生的依据:哈希码并不是完全唯一的,它是一种算法,让同一个类的对象按照自己不同的特征尽量的有不同的哈希码,但不表示不同的对象哈希码完全不同。也有相同的情况,看程序员如何写哈希码的算法。
简单理解就是一套算法算出来的一个值,且这个值对于这个对象相对唯一。哈希算法有一个协定:在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行hashcode比较时所用的信息没有被修改。(ps:要是每次都返回不一样的,就没法玩儿了)
两个对象值相同(x.equals(y) == true),但却可有不同的hash code,这句话对不对?
不对,如果两个对象x和y满足x.equals(y) == true,它们的哈希码(hash code)应当相同。Java对于eqauls方法和hashCode方法是这样规定的:(1)如果两个对象相同(equals方法返回true),那么它们的hashCode值一定要相同;(2)如果两个对象的hashCode相同,它们并不一定相同。当然,你未必要按照要求去做,但是如果你违背了上述原则就会发现在使用容器时,相同的对象可以出现在Set集合中,同时增加新元素的效率会大大下降(对于使用哈希存储的系统,如果哈希码频繁的冲突将会造成存取性能急剧下降)
可以在hashcode中使用随机数字吗?
不行,因为同一对象的 hashcode 值必须是相同的
重写equals方法的时候为什么需要重写hashcode
首先来看一段代码:
public class HashMapTest {
private int a;
public HashMapTest(int a) {
this.a = a;
}
public static void main(String[] args) {
Map<HashMapTest, Integer> map = new HashMap<HashMapTest, Integer>();
HashMapTest instance = new HashMapTest(1);
map.put(instance, 1);
Integer value = map.get(new HashMapTest(1));
if (value != null) {
System.out.println(value);
} else {
System.out.println("value is null");
}
}
}
//程序运行结果: value is null
简单说下HashMap的原理,HashMap存储数据的时候,是取的key值的哈希值,然后计算数组下标,采用链地址法解决冲突,然后进行存储;取数据的时候,依然是先要获取到hash值,找到数组下标,然后for遍历链表集合,进行比较是否有对应的key。比较关心的有2点:1.不管是put还是get的时候,都需要得到key的哈希值,去定位key的数组下标; 2.在get的时候,需要调用equals方法比较是否有相等的key存储过。
反过来,我们再分析上面那段代码,Map的key是我们自己定义的一个类,可以看到,我们没有重写equal方法,更没重写hashCode方法,意思是map在进行存储的时候是调用的Object类中equals()和hashCode()方法。为了证实,我们打印下hashCode码。
public class HashMapTest {
private Integer a;
public HashMapTest(int a) {
this.a = a;
}
public static void main(String[] args) {
Map<HashMapTest, Integer> map = new HashMap<HashMapTest, Integer>();
HashMapTest instance = new HashMapTest(1);
System.out.println("instance.hashcode:" + instance.hashCode());
map.put(instance, 1);
HashMapTest newInstance = new HashMapTest(1);
System.out.println("newInstance.hashcode:" + newInstance.hashCode());
Integer value = map.get(newInstance);
if (value != null) {
System.out.println(value);
} else {
System.out.println("value is null");
}
}
}
//运行结果:
//instance.hashcode:929338653
//newInstance.hashcode:1259475182
//value is null
不出所料,hashCode不一致,所以对于为什么拿不到数据就很清楚了。这2个key,在Map计算的时候,可能数组下标就不一致,就算数据下标碰巧一致,根据前面,最后equals比较的时候也不可能相等(很显然,这是2个对象,在堆上的地址必定不一样)。我们继续往下看,假如我们重写了equals方法,将这2个对象都put进去,根据map的原理,只要是key一样,后面的值会替换前面的值,接下来我们实验下:
public class HashMapTest {
private Integer a;
public HashMapTest(int a) {
this.a = a;
}
public static void main(String[] args) {
Map<HashMapTest, Integer> map = new HashMap<HashMapTest, Integer>();
HashMapTest instance = new HashMapTest(1);
HashMapTest newInstance = new HashMapTest(1);
map.put(instance, 1);
map.put(newInstance, 2);
Integer value = map.get(instance);
System.out.println("instance value:"+value);
Integer value1 = map.get(newInstance);
System.out.println("newInstance value:"+value1);
}
public boolean equals(Object o) {
if(o == this) {
return true;
} else if(!(o instanceof HashMapTest)) {
return false;
} else {
HashMapTest other = (HashMapTest)o;
if(!other.canEqual(this)) {
return false;
} else {
Integer this$data = this.getA();
Integer other$data = other.getA();
if(this$data == null) {
if(other$data != null) {
return false;
}
} else if(!this$data.equals(other$data)) {
return false;
}
return true;
}
}
}
protected boolean canEqual(Object other) {
return other instanceof HashMapTest;
}
public void setA(Integer a) {
this.a = a;
}
public Integer getA() {
return a;
}
}
//运行结果:
//instance value:1
//newInstance value:2
你会发现,不对呀?同样的一个对象,为什么在map中存了2份,map的key值不是不能重复的么?没错,它就是存的2份,只不过在它看来,这2个的key是不一样的,因为他们的哈希码就是不一样的,可以自己测试下,上面打印的hash码确实不一样。那怎么办?只有重写hashCode()方法,更改后的代码如下:
public class HashMapTest {
private Integer a;
public HashMapTest(int a) {
this.a = a;
}
public static void main(String[] args) {
Map<HashMapTest, Integer> map = new HashMap<HashMapTest, Integer>();
HashMapTest instance = new HashMapTest(1);
System.out.println("instance.hashcode:" + instance.hashCode());
HashMapTest newInstance = new HashMapTest(1);
System.out.println("newInstance.hashcode:" + newInstance.hashCode());
map.put(instance, 1);
map.put(newInstance, 2);
Integer value = map.get(instance);
System.out.println("instance value:"+value);
Integer value1 = map.get(newInstance);
System.out.println("newInstance value:"+value1);
}
public boolean equals(Object o) {
if(o == this) {
return true;
} else if(!(o instanceof HashMapTest)) {
return false;
} else {
HashMapTest other = (HashMapTest)o;
if(!other.canEqual(this)) {
return false;
} else {
Integer this$data = this.getA();
Integer other$data = other.getA();
if(this$data == null) {
if(other$data != null) {
return false;
}
} else if(!this$data.equals(other$data)) {
return false;
}
return true;
}
}
}
protected boolean canEqual(Object other) {
return other instanceof HashMapTest;
}
public void setA(Integer a) {
this.a = a;
}
public Integer getA() {
return a;
}
public int hashCode() {
boolean PRIME = true;
byte result = 1;
Integer $data = this.getA();
int result1 = result * 59 + ($data == null?43:$data.hashCode());
return result1;
}
}
//运行结果:
//instance.hashcode:60
//newInstance.hashcode:60
//instance value:2
//newInstance value:2
可以看到,他们的hash码是一致的,且最后的结果也是预期的。
曾经同事趟过这坑,Map中存了2个数值一样的key,所以大家谨记哟! 在重写equals方法的时候,一定要重写hashCode方法。
网友评论