equals方法深入解析

写在前面

  • 最初接触java的时候,都会有涉及equals==的区别,最经典的案例就是用String类型的数据作类比。最常见的说法就是:equals比较的是值,==比较的是引用地址。
  • 首先这种说法是错误的,也有人认为这种说法是不完全正确的(至少对于String这个类来说这种说法是没问题的)。
  • 之所以说这种说法是错误的,是因为本人真的觉得这个总结实在是误人子弟。

1、equals==

1.1、==

首先我们要先清楚java中有哪些数据类型,及他们的存储位置。

  • java中的数据类型:
    • (1)基本数据类型:整数类型(byte,short,int,long)、浮点类型(float,double)、字符型(char)、布尔型(Boolean)
    • (2)引用数据类型:类(class)、接口(interface)、数组
  • 存储位置:
    • 基础数据类型:存储在常量池中(JDK8之后去除了元方法区,改为存在堆内存中的元空间)
    • 引用数据类型:存储于堆中。

在java中,==比较的内容也跟数据所处的位置相关:

  • (1)基础数据类型:比较的是值。
  • (2)引用数据类型:比较的是是对象的引用地址。

为什么会这样分?我们就需要来看一个例子。

  • 示例代码:
    public class StringTest {
    	String str1 = "字符串";
    	String str2 = "字符串";			
    }
    
    public class StringTest1 {
    String str3 = new String("字符串");
    String str4 = new String("字符串");
    }
    

首先来看一上述两段代码有什么不同?一个是直接赋值,一个是给对象赋值。我们通过javac把上述两段代码编译为.class文件,再用javap方法来反编译看一下:

  • StringTest对应的反编译文件
    public StringTest();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=2, locals=1, args_size=1
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."":()V
             4: aload_0
             5: ldc           #2                  // String 字符串
             7: putfield      #3                  // Field str1:Ljava/lang/String;
            10: aload_0
            11: ldc           #2                  // String 字符串
            13: putfield      #4                  // Field str2:Ljava/lang/String;
            16: return
        LineNumberTable:
            line 1: 0
            line 3: 4
            line 4: 10
    

}
```

  • StringTest1 对应的反编译文件
    	public StringTest1();
    	    descriptor: ()V
    	    flags: ACC_PUBLIC
    	    Code:
    	      stack=4, locals=1, args_size=1
    	         0: aload_0
    	         1: invokespecial #1                  // Method java/lang/Object."":()V
    	         4: aload_0
    	         5: new           #2                  // class java/lang/String
    	         8: dup
    	         9: ldc           #3                  // String 字符串
    	        11: invokespecial #4                  // Method java/lang/String."":(Ljava/lang/String;)V
    	        14: putfield      #5                  // Field str3:Ljava/lang/String;
    	        17: aload_0
    	        18: new           #2                  // class java/lang/String
    	        21: dup
    	        22: ldc           #3                  // String 字符串
    	        24: invokespecial #4                  // Method java/lang/String."":(Ljava/lang/String;)V
    	        27: putfield      #6                  // Field str4:Ljava/lang/String;
    	        30: return
    	      LineNumberTable:
    	        line 1: 0
    	        line 3: 4
    	        line 4: 17
    	}
    
  • 分析结果:
    从反编译文件可以看出,StringTest并没有新建对象,仅做了赋值。StringTest1这段代码中new了两个String对象。也就是说:
    • StringTest中的str1 和str2直接指向常量池中的"字符串"这个值;
    • StringTest1中的str3 和str4分别指向堆中的两个String对象,String对象再指向常量池中的值。如下图:
      equals方法深入解析_第1张图片
  • 结论:
    • 基础数据类型是加载完成后,直接赋值,指向常量池中的数据。所以==方法比较的是常量池中的值;
    • 引用数据加载完成之后,会在堆中创建一个对象,其对象本身指向常量池中的数据,对象不一样,结果也就不一样。所以说==比较的是引用地址。

1.2 equals方法

java中,equals方法存在的作用是允许程序员自己根据需要定义比较方法。也就是说由程序员决定什么情况两个对象是相等的。为什么String中的equals()方法比较的是值,这种说法是正确的呢?
首先我们来了解一下java中所有类的祖先:Object的equals方法

1.2.1 Object的equals方法

Object是Java中最原始的类,在Object中,有默认的hashCode和equals方法实现,二者是相互关联的。 JDK中Object.java定义了这两个方法:

public class Object {
	......
    public native int hashCode();
    public boolean equals(Object obj) {
        return (this == obj);
    }
    ......
    }

其中hashCode方法是native方法,具体是实现是返回一个对象的内存地址作为其hashcode。equals方法则是简单的直接比较两个对象的地址。

1.2.2 String的equals方法

String类继承自Object类,并覆写了其equals方法,仅对值做了循环匹配,所以才会有String的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;
}
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;
    }

看过String源码就会发现,String类不仅覆写了equals代码,还覆写了hashCode的值。这是因为java常规协定关于equals方法的协定,规定了覆写equals方法的同时,必须覆写hashCode。协定内容想看第2章总结。

1.2.3 示例代码

代码中Student2 这个类覆写了equals方法,但是没有覆写hashCode。

public class EqualsTest {
	public static void main(String[] args) {
		
		//String类
		System.out.println("常量直接赋值=====");
		String str1 = new String("字符串");
		String str2 = new String("字符串");
			System.out.println(str2.hashCode());
			System.out.println(str1.hashCode());
			System.out.println(str1.equals(str2));
		
		//未重写equals方法	
		System.out.println("未重写equals方法=====");
		Student1 student1 = new Student1("name");
		Student1 student2 = new Student1("name");
			System.out.println(student2.hashCode());
			System.out.println(student1.hashCode());
			System.out.println(student1.equals(student2));
			
		
			//重写equals方法	
			System.out.println("重写equals方法=====");
		Student2 student3 = new Student2("name");
		Student2 student4 = new Student2("name");
			System.out.println(student3.hashCode());
			System.out.println(student4.hashCode());
			System.out.println(student3.equals(student4));
	}

}
class Student1 {
    private String name;
    public Student1(String name) {
        this.name = name;
    }
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
}
class Student2 {
	private String name;
    public Student2(String name) {
        this.name = name;
    }
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Student2 other = (Student2) obj;
        if (name != other.name)
            return false;
        return true;
    }
}

执行结果

String类=====
23468387
23468387
true

未重写equals方法=====
366712642
1829164700
false

重写equals方法=====
2018699554
1311053135
true

分析:从结果可以看到Student2 未覆写hashCode所以两个对象的hash值不一样,但因为重写了equals方法所以equals比较结果为true.

2、总结

  • (1)==针对基础数据类型比较的是值,针对引用数据类型比较的是引用地址;
  • (2)equals()方法由开发人员自己定义对象的相等方式,重写equals方法需要遵循协定:
    • ①两个对象相等,hashcode一定相等
    • ②两个对象不等,hashcode不一定不等
    • ③hashcode相等,两个对象不一定相等
    • ④hashcode不等,两个对象一定不等

你可能感兴趣的:(java基础,java)