在我的认识中==和equals的区别可以简单概括为:==比较的是内存地址是否一致;equals比较的是对象的值是否一致。但是在深入了解后发现不能这么简单的进行区分。
==在比较时针对对数据类型分为两类情况:
1、比较基本数据类型时(整数、浮点、字符、布尔):比较的是值是否一致
2、比较引用数据类型时:比较的是内存地址是否一致
equals在比较时同样也需要分为两类情况:
首先需要知道,Java中所有的类都是继承于Object这个超类的,因此在Object类中,已经有定义过一个equals方法,可以看到该equals方法实际上也还是通过==实现的比较,比较的是对象的内存地址是否一致,一般情况下意义不大,因此在一些类库当中这个方法被重写过了,例如String、Integer、Date等。像比较常用的String的equals方法,可以看到在重写equals方法后比较的就是对象的值。
// Object.java
public boolean equals(Object obj) {
return (this == obj);
}
// String.java
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;
}
因此
1、如果equals方法没有被重写过:比较的内存地址是否一致
2、如果equals方法被重写过:按照被重写的equals方法进行比较,如String的equals,比较的就是对象的值
public class Test {
public static void main(String args[]) {
Person person1 = new Person("name", "age");
Person person2 = new Person("name", "age");
// 如果Person类中不重写equals方法,因为Object类中的equals方法比较的是内存地址是否一致,返回false
// 如果Person类中重写了equals方法,那么按照重写的方法,比较的是Person对象中的name和age字段,返回true
System.out.println(person1.equals(person2));
}
}
public class Person {
private String name;
private String age;
public Person(String name, String age) {
this.name = name;
this.age = age;
}
// 重写Object类中的equals方法
@Override
public boolean equals(Object obj){
// 如果两个内存地址相同,那么一定是指向同一个对内存中的对象,就无需比较两个对象的属性值
if(this == obj){
return true;
}
// 判断obj是不是Person的一个对象
// 如果是,做向下转型
// 如果不是,直接返回false。
if(!(obj instanceof Person)){
return false;
}
// 比较Person对象中的name和age字段值是否一致
Person person = (Person)obj;
return this.name.equals(person.name) && this.age.equals(person.age);
}
// 重写equals方法时必须重写hashCode方法
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
上述代码中,如果没有在Person类中重写equals方法,那么比较时就会执行Object类中的equals方法,直接比较内存地址是否一致;如果有特定需求,那么就需要重写equals方法(重写equals方法时必须重写hashCode方法),此时再执行equals方法时就按照重写的逻辑进行比较判断。
public class Test {
public static void main(String args[]) {
String str1 = "str";
String str2 = "str";
String str3 = new String("str");
String str4 = new String("str");
str4 = str4.intern();
System.out.println(str1 == str2); // true
System.out.println(str1.equals(str2)); // true
System.out.println(str1 == str3); // false
System.out.println(str1.equals(str3)); // true
System.out.println(str1 == str4); // true
System.out.println(str1.equals(str4)); // true
}
}

str1、str2指向的是同一个字符串对象,该字符串对象存放在常量池中,因此str1 == str2判断为true。
str3通过new String(“str”)创建对象,会先向常量池中添加一个”str”字符串对象( 如果常量池中“str”字符串不存在 ),然后在堆内存中新创建一个存储了“str”的String对象。str3指向这个堆内存中的String对象,str1指向常量池中的字符串对象,因此str1 == str3判断为false。
在JDK8中,String调用了intern()方法,如果常量池先前已创建出该字符串对象,则返回常量池中的该字符串的引用。否则,如果该字符串对象已存在与Java堆中,则将堆中对此对象的引用添加到字符串常量池中,并且返回该引用;如果堆中不存在,则在常量池中创建该字符串并返回其引用。
str4调用 intern方法时,因为常量池已经存在”str”字符串,所以直接返回该字符串的引用,此时str1、str4指向常量池中的字符串对象,因此str1 == str4判断为true。
另外:
1、通过String str = “str”方式创建对象,会将”str”字符串放入常量池(如果常量池中“str”字符串不存在),相当于创建了一个对象。
2、通过String str = new String(“str”)方式创建对象, 会将”str”字符串放入常量池(如果常量池中“str”字符串不存在) ,然后在堆内存中新创建一个存储了“str”的String对象 ,相当于创建了两个对象。