Java Equals方法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u012934325/article/details/83077253

Object类中有一个equals方法,该方法是用来检测两个对象是否相等,在Object类中,该方法的实现是对比两个对象的引用是否相等,而非对比内容是否相等,具体的实现如下:

 public boolean equals(Object obj) {
    return (this == obj);
}

一个简单的case,来说明Object类中的equals方法是比较的引用还是内容,具体代码如下所示:

public class TestEquals extends Object{
public static void main(String[] args) {
	TestEquals t1 = new TestEquals();
	TestEquals t2 = new TestEquals();
	System.out.println(t1.equals(t2));
	System.out.println(t1 == t2);
}}

运行结果如下所示:

false
false

我们创建了两个TestEquals对象,它们的内存地址不同,所以==的结果为false,但是为什么equals的内容也为空,按照道理t2和t1都应该为空对象,equals的比较结果应该为true才对,此时我们可以看看Object类中的equals方法实现,它对比的两个引用的地址是否相同,如果引用的地址在内存中是相同的,那么比较的结果才会返回true。


我们再来看一个例子,具体代码如下:

public class TestEquals extends Object{
	public static void main(String[] args) {
		TestEquals t1 = new TestEquals();
		TestEquals t2 = new TestEquals();
		System.out.println(t1.equals(t2));
		System.out.println(t1 == t2);
		String str1 = new String("abc");
		String str2 =  new String("abc");
		System.out.println(str1.equals(str2));
		System.out.println(str1 == str2);
	}
}

运行结果如右所示:false false true false

从结果我们可以看出来,str1.equals(str2)返回值为true,str1==str2返回值为false,原因很简单,因为String类重写了Object类的equals方法,所以它的equals对比的是内容,而非对比的引用的地址。后者是因为两个对象所new出来的内存地址不同,所以返回false。String类的equals实现如下:

public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String) anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                            return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

从上述代码可以看出,里面有两个if判断,第一个是判断是否内存地址一样,第二个判断对象类型是否为String类型,此处用了类型检查符instanceof来检查对象是否为String类型,接着将两个String对象转为字符数组,然后循环对比字符数组的每个元素,如果有一个元素不等则立马返回false,equals方法也因此结束,只有当两个对象的所有元素都相等时,才会返回true。

因此,我们可以看出,当该类是默认继承Object类的equals方法时,它的equals和==都是用来比较对象的内存地址的,如果内存地址相同,则返回true;反之返回false。如果该类需要使用equals比较内容,那么我们需要重写一下equals方法,比如String类,它就重写了Object类的equals方法,这样才可以比较两个对象的内容是否equals。


上面提到了如果一个类继承了Object类的equals方法,当我们需要使用equals时,我们需要重写一下equals方法,那么我们该如何重写equals方法呢?

关于equals方法的重写规则,我们可以从oracle官网看到相应的英文解释:

oracle官网对Object类equals方法的重写规则解释

  1. 自反性。对于任何非null的引用值x,x.equals(x)应返回true。
  2. 对称性。对于任何非null的引用值x与y,当且仅当y.equals(x)返回true时,x.equals(y)才返回true。
  3. 传递性。对于任何非null的引用值x、y与z,如果y.equals(x)返回true,y.equals(z)返回true,那么x.equals(z)也应返回true。
  4. 一致性。对于任何非null的引用值x与y,假设对象equals比较中的信息没有被修改,则多次调用x.equals(y)始终返回true或者始终返回false。
  5. 对于任何非空引用值x,x.equal(null)应返回false。
 package com.lee.java;

import java.util.Iterator;

public class Dog {

	private String name;
	public  Dog(String name) {
		this.name = name;
	}
	public static void main(String[] args) {
		Dog d1 = new Dog("peter");
		Dog d2 = new Dog("peter");
		Dog d3 = new Dog("peter");
		System.out.println("自反性:d1.equals(d2)" + d1.equals(d2));
		System.out.println("对称性:d1.equals(d2)" + d1.equals(d2));
		System.out.println("对称性:d2.equals(d1)" + d2.equals(d1));
		System.out.println("传递性:d1.equals(d2)" + d1.equals(d2));
		System.out.println("传递性:d2.equals(d3)" + d2.equals(d3));
		System.out.println("传递性:d1.equals(d3)" + d1.equals(d3));
		System.out.println("一致性:");
		for (int i=0;i<10;i++) {
			if(d1.equals(d2)!=d1.equals(d2)){
				System.out.println("没有遵守一致性原则");
			}
		}
		System.out.println("遵守了一致性原则");
		System.out.println("与null比较:" + d1.equals(null));
	}

}
自反性:d1.equals(d2)false
对称性:d1.equals(d2)false
对称性:d2.equals(d1)false
传递性:d1.equals(d2)false
传递性:d2.equals(d3)false
传递性:d1.equals(d3)false
一致性:
遵守了一致性原则
与null比较:false

接下来我们看一下两个继承关系之间的父子类的equals方法,具体实现代码如下:

package com.lee.java;

public class Animal {
	private String name;

	@Override
	public boolean equals(Object obj) {
		if(obj instanceof Animal){
			Animal animal = (Animal)obj;
			return name == animal.name;
		}
		return false;
	}

	public Animal(String name) {
		this.name = name;
	}
	
}

package com.lee.java;

public class Dog extends Animal {
	private Integer teeth;

	public Dog(Integer teeth, String name) {
		super(name);
		this.teeth = teeth;
	}

	public static void main(String[] args) {
		Animal animal = new Animal("lucy");
		Dog dog = new Dog(20, "lucy");
		System.out.println(animal.equals(dog));
		System.out.println(dog.equals(animal));
	}

	@Override
	public boolean equals(Object obj) {
		if (obj instanceof Dog) {
			Dog d = (Dog) obj;
			return super.equals(d) && teeth == d.teeth;
		}
		return false;
	}

}

运行结果:
true false

从结果我们可以看出animal.equals(dog)返回值是true,dog.equals(animal)返回值是false,当把dog对象传入到父类animal类的equals方法中,我们可以看出其符合if判断并且返回true,因为dog类是animal类的子类,因此dog instanceof Animal返回true。反之,当把animal对象传入到dog类的equals方法时,animal对象并不属于Dog类的对象,所以此时的animal instanceof Dog会返回false。综合分析,我们可以知道前面所述就是导致true和false的原因所在。


equals和==之间的区别:

  1. equals是一个方法,==是一个操作符
  2. ==是用来比较引用或者比较地址,equals比较的是内容。简单的来说就是==比较两个对象是否指向的是内存中的同一块地址,equals比较的是两个对象所指向地址的内容来进行对比。
  3. 如果一个类默认继承的Object类,且没有重写equals方法,那么使用equals和==是等价的。

重写equals方法的时候为何要重写hashcode方法?

简单了解一下hashcode方法,属于Object类,hashcode方法是根据对象来计算内存的地址值,也就是哈希码。Object类中的hashcode方法的具体实现:

 public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

接下来我们看一个例子,具体实现代码如下:

package com.java;

public class TestHashCode {

	public static void main(String[] args) {
		String str1 = "peter";
		StringBuilder sb1 = new StringBuilder(str1);
		String str2 = new String("peter");
		StringBuilder sb2 = new StringBuilder(str2);
		System.out.println(str1.hashCode() + "===" + sb1.hashCode());
		System.out.println(str2.hashCode() + "===" + sb2.hashCode());
	}
}

运行结果:

106557964===1537587829
106557964===1567917652

从运行结果我们可以看出str1和str2的hashcode是相同的,但是sb1和sb2的hashcode是不一样的。出于什么原因呢?类似于equals方法,因为StringBuilder类并没有重写Object类的hashcode方法,所以StringBuilder的sb1和sb2调用的hashcode方法是OBject类的hashcode来计算其地址的,所以导致hashcode是不一样的。

那么我们应该如何来重写equals的hashcode方法呢?

  1. 只要合理地组织对象的散列码,就能够让不同的对象产生比较均匀的散列码。
  2. java 7中对hashCode方法做了两个改进,首先java发布者希望我们使用更加安全的调用方式来返回散列码,也就是使用null安全的方法java.util.Objects.hashCode(注意不是Object而是)方法,这个方法的优点是如果参数为null,就只返回0,否则返回对象参数调用的hashCode的结果。
  3. java.util.Objects.hash方法

下面是Objects类的hashcode和hash方法源码实现:

hashcode方法返回一个int类型的数值,参数是一个对象Object:

 public static int hashCode(Object o) {
        return o != null ? o.hashCode() : 0;
    }

hash方法也返回一个int类型数值,只是它的参数是可变参数的对象,允许有多个对象作为参数传入进来:

public static int hash(Object... values) {
        return Arrays.hashCode(values);
    }

重写equals方法时,需要注意用instanceof还是用getClass方法:

在重写equals() 方法时,一般都是推荐使用 getClass 来进行类型判断(除非所有的子类有统一的语义才使用instanceof),不是使用 instanceof。我们都知道 instanceof 的作用是判断其左边对象是否为其右边类的实例,返回 boolean 类型的数据。可以用来判断继承中的子类的实例是否为父类的实现


总结一下:

  1. Object类中的equals和==的含义
  2. 如何重写equals方法
  3. 重写equals方法时需要遵守的规则
  4. 重写equals方法时为何要重写hashcode方法
  5. 如何重写hashcode方法
  6. 重写equals时getClass方法和instanceof的使用

猜你喜欢

转载自blog.csdn.net/u012934325/article/details/83077253