JAVA当中所有的类都是继承于Object这个基类的,在Object中的基类中定义了一个equals的方法,这个方法的初始行为是比较对象的内存地址,但在一些类库当中这个方法被覆盖掉了,如String,Integer,Date在这些类当中equals有其自身的实现(重写覆盖了原来的),而不再是比较类在堆内存中的存放地址了。     

equals方法,比较两个对象是否相等,比较的是地址(初始)和值(被重写后)
     * public boolean equals(Object obj){
     *    return (this == obj);
     * }
     * 
     * Object中的equals方法是比较对象的地址值,没有什么意义,需要重写它。
     * 因为在开发中我们通常比较的是对象中的属性值,我们认为相同属性是同一个对象,这样我们就需要重写equals方法。
   
       ==和equals方法的区别
           共同点:都可以做比较,返回值都是boolean
           区别:1,==是比较运算符号,既可以比较基本数据类型,也可以比较引用数据类型,基本数据类型比较的是值,引用数据类型比较的是地址值
             2,equals方法只能比较引用数据类型,equals方法在没有重写之前,比较的是地址值,底层依赖的是==号,但是比较地址值是没有意义的,我们需要重写equals方法比较对象中的属性值。默认情况下也就是从超类Object继承而来的equals方法与‘==’是完全等价的,比较的都是对象的内存地址,但我们可以重写equals方法,使其按照我们的需求的方式进行比较,如String类重写了equals方法,使其比较的是字符的序列,而不再是内存地址。

     在覆写 equals 时推荐使用 getClass 进行类型判断。而不是使用 instanceof。因为使用instanceof判断其左边对象是否为其右边类的实例,也可以用来判断继承中的子类的实例是否为父类的实现。如果两者存在继承关系,肯定会返回 true 。所以用instanceof不太好。

equals()的重写规则:

  • 自反性。对于任何非null的引用值x,x.equals(x)应返回true。
  • 对称性。对于任何非null的引用值x与y,当且仅当:y.equals(x)返回true时,x.equals(y)才返回true。
  • 传递性。对于任何非null的引用值x、y与z,如果y.equals(x)返回true,y.equals(z)返回true,那么x.equals(z)也应返回true。
static class B extends A{
		@Override
		public boolean equals(Object obj) {
			if(obj instanceof B){
				return true;
			}
			return super.equals(obj);
		}
	}
  • 一致性。对于任何非null的引用值x与y,假设对象上equals比较中的信息没有被修改,则多次调用x.equals(y)始终返回true或者始终返回false。
  • 对于任何非空引用值x,x.equal(null)应返回false。
//import java.util.Objects;
public class Test {

	/**
	 * @param args
	 * equals方法,比较两个对象是否相等
	 * public boolean equals(Object obj){
	 *    return (this == obj);
	 * }
	 * 
	 * Object中的equals方法是比较对象的地址值,没有什么意义,需要重写它。
	 * 因为在开发中我们通常比较的是对象中的属性值,我们认为相同属性是同一个对象,这样我们就需要重写equals方法。
	
	   ==和equals方法的区别
	       共同点:都可以做比较,返回值都是boolean
	        区别:1,==是比较运算符号,既可以比较基本数据类型,也可以比较引用数据类型,基本数据类型比较的是值,引用数据类型比较的是地址值
	 	    2,equals方法只能比较引用数据类型,equals方法在没有重写之前,比较的是地址值,底层依赖的是==号,但是比较地址值是没
	 	               有意义的,我们需要重写equals方法比较对象中的属性值。
	 	    
	 */
	public static void main(String[] args) {

		Student s1 =  new Student("a", 1);
		Student s2 =  new Student("b", 2);
		boolean b = s1.equals(s2);   			//比较2个对象是否相等
		System.out.println(s1 == s2);
		System.out.println(b);					//重写之后比较的是对象中的属性值
	}
}

class Student {

	private String name;
	private int age;

	public Student() {   					
		super();
	}

	public Student(String name, int age) {  	
		super();
		this.name = name;
		this.age = age;
	}

	public String getName() {				
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	/*@Override
	public String toString() {
		return "姓名是:" + name + ",年龄是:"+ age;
	}*/
	
	@Override
	public String toString() {
		return "Student [name=" + name + ", age=" + age + "]";
	}

	@Override
	//重写equals方法
	public boolean equals(Object obj) {
	     if (obj != null && obj.getClass() == this.getClass()) {  //if (obj instanceof Student) {
		Student s = (Student)obj;
		if (s.getName() == null || this.name == null) {
                return false;
		}
        else
		return this.name.equals(s.name) &&this.age == s.age;
	    }
	    return false;
	}
	
    @Override
	public int hashCode() {
	    return name.hashCode()+new Integer(age).hashCode();
		//return Objects.hash(name,age);
	}


}

输出:

false
false

下面是 String 的 equals() 方法:

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;
        }

为什么重写equals()的同时还得重写hashCode():

这个问题主要是针对映射相关的操作(Map等接口)。学过数据结构的同学都知道Map接口的类会使用到键对象的哈希码,当我们调用put方法或者get方法对Map容器进行操作时,都是根据键对象的哈希码来计算存储位置的,因此如果我们对哈希码的获取没有相关保证,就可能会得不到预期的结果。在java中,我们可以使用hashCode()来获取对象的哈希码,其值就是对象的存储地址,这个方法在Object类中声明,因此所有的子类都含有该方法。hashCode的意思就是散列码,也就是哈希码,是由对象导出的一个整型值,散列码是没有规律的,如果x与y是两个不同的对象,那么x.hashCode()与y.hashCode()基本是不会相同的.

 

编写一个完美的重写equals()方法的几点建议

下面给出编写一个完美的equals方法的建议(出自Java核心技术 第一卷:基础知识):

1)显式参数命名为otherObject,稍后需要将它转换成另一个叫做other的变量(参数名命名,强制转换请参考建议5)

2)检测this与otherObject是否引用同一个对象 :if(this == otherObject) return true;(存储地址相同,肯定是同个对象,直接返回true)

3) 检测otherObject是否为null ,如果为null,返回false.if(otherObject == null) return false;

4) 比较this与otherObject是否属于同一个类(视需求而选择)

  • 如果equals的语义在每个子类中有所改变,就使用getClass检测 :if(getClass()!=otherObject.getClass()) return false;
  • 如果所有的子类都拥有统一的语义,就使用instanceof检测 :if(!(otherObject instanceof ClassName)) return false;

5) 将otherObject转换为相应的类类型变量:ClassName other = (ClassName) otherObject;

6) 现在开始对所有需要比较的域进行比较 。使用==比较基本类型域,使用equals比较对象域。如果所有的域都匹配,就返回true,否则就返回flase。

  • 如果在子类中重新定义equals,就要在其中包含调用super.equals(other)
  • 当此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明 相等对象必须具有相等的哈希码 。