equals和==的区别
概括
equals
和==
都是比较内存单元中存储的内容。==
可以用于基本类型和引用类型的比较。equals
只能用于引用类型的比较。
对于某些类比如String,对equals
进行了重写,不再是比较内存单元中存储的内容。
==
用于比较内存单元中存储的内容
1)基本类型范例
public static void main(String[] args) {
int a =10;
int b =10;
System.out.println( a == b); // true
b=50;
System.out.println( a == b); // false
}
执行 int a =10;int b =10;
时,JVM在内存中为变量a分配存储单元并填入值10,为变量b分配存储单元并填入值10。在内存单元中,变量a和变量b占据不同的内存单元,但是内存单元存储的值都是10,是相同的,所以返回true。
a b
│ │
▼ ▼
┌───┬───┬───┬───┬───┬───┬───┐
│ │10 │ │ │ │10 │ │
└───┴───┴───┴───┴───┴───┴───┘
执行 b=50;
时,JVM在把新的值50写入变量b的存储单元中,旧值被覆盖。此时变量a和变量b内存单元存储的值不相同,所以返回false。
a b
│ │
▼ ▼
┌───┬───┬───┬───┬───┬───┬───┐
│ │10 │ │ │ │50 │ │
└───┴───┴───┴───┴───┴───┴───┘
2)引用类型范例
public static void main(String[] args) {
String x="hello";
String y= "HELLO";
String z="hello";
System.out.println( x == y.toLowerCase()); // false
System.out.println( x == z); // true
}
对于引用类型,JVM在内存中为变量分配的存储单元存储的是引用类型的地址。
执行 String x="hello"; String y= "HELLO";
时,JVM在内存单元中创建了两个字符串对象,然后为变量x和变量y分配两个存储单元,这两个存储单元存储的内容是两个字符串对象的内存地址。内存地址不同,所以比较结果为false。
┌─────────────────────────────────────────┐
│ │
│ ▼
┌───┬───┬───┬───┬─┴─┬───────┬───┬───────┬───┬───────┬───┬───────┬───┐
│ │ x │ │ │ y │ │ │"hello"│ │ │ │"HELLO"│ │
└───┴─┬─┴───┴───┴───┴───────┴───┴───────┴───┴───────┴───┴───────┴───┘
│ ▲
└─────────────────────────────┘
执行 System.out.println( x == z);
结果为true,这涉及到了内存中的常量池。其实上面执行String x="hello"; String y= "HELLO";
的时候都是先去常量池查找是否存在该字符串,如果常量池中没有才会创建字符串对象并返回该字符串对象的引用。所以在执行 String z="hello";
,先去常量池查找,发现前面已经创建过 “hello” 这个字符串,所以直接把该字符串的引用返回给z。因此x和z存储的都是同一个字符串的 引用/内存地址
,也就是x和z在内存单元中存储的内容相同,所以比较结果为true。
┌─────────────────────────────────────────┐
│ │
│ ▼
┌───┬───┬───┬───┬─┴─┬───────┬───┬───────┬───┬───────┬───┬───────┬───┐
│ │ x │ │ │ y │ │ │"hello"│ │ │ z │"HELLO"│ │
└───┴─┬─┴───┴───┴───┴───────┴───┴───────┴───┴───────┴─┬─┴───────┴───┘
│ ▲ ▲ │
└───────────────────────────│─┘ │
└───────────────────┘
equals
用于比较内存单元中存储的内容
1)源码解析
equals
也是比较内存单元中存储的内容,不过只能用于引用类型的比较,不能用于基本类型。
其实查看源码很好理解,如下,equals方法本身是调用 ==
方法,而 ==
方法比较的是内存单元中存储的内容,所以equals方法也是用于比较内存单元中存储的内容。由于方法参数是Object类型,所以只能接受对象也就是引用类型。
public boolean equals(Object obj) {
return (this == obj);
}
2)demo程序
下面是demo程序,返回结果为false。
class Student{
int age;
String name;
public Student(int age,String name){
this.age = age;
this.name = name;
}
}
public class Hello {
public static void main(String[] args) {
Student stu1 = new Student(10,"张三");
Student stu2 = new Student(20,"李四");
System.out.println(stu1.equals(stu2)); // false
}
}
被重写的 equals
方法
1)demo
public static void main(String[] args) {
String x="hello";
String y= new String("hello");
System.out.println( x.equals(y) ); // true
}
String y= new String("hello");
通过new方法创建实例,就不会去常量池中查找是否已经存在 “hello” 字符串,而是创建一个新的字符串,并把新字符串的引用赋值给变量y。所以x和y存储的是两个字符串对象的 引用/内存地址,也就是变量x和变量y在内存单元中存储的内容是不同的。
虽然内存单元中存储的内容不同,但是String类对equals
方法进行了重写,所以返回的结果并不是false。
2)analysis
String的equals方法源码实现如下:
private final byte coder;
static final boolean COMPACT_STRINGS;
/*
* 该函数用于获取coder属性的值
* 如果COMPACT_STRINGS为true,函数返回coder的值,否则coder默认值为UTF16
*/
byte coder() {
return COMPACT_STRINGS ? coder : UTF16;
}
private boolean isLatin1() {
return COMPACT_STRINGS && coder == LATIN1;
}
...
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String aString = (String)anObject;
if (coder() == aString.coder()) {
return isLatin1() ? StringLatin1.equals(value, aString.value)
: StringUTF16.equals(value, aString.value);
}
}
return false;
}
可以看到先使用 ==
判断内存单元中存储的内容是否相同,由于x和y存储的是不同对象的内存地址,所以 this == anObject
不成立,但是源码中并没有直接 return false,而是继续向下判断。如果被比较的对象y不是字符串的实例直接返回false。如果对象y是字符串的实例,执行if语句块,就判断两个字符串的coder属性值是否相等,该属性的取值取值有两种可能: LATIN1或UTF16。
coder() == aString.coder()
等价于this.coder() == aString.coder()
。
如果两个字符串的coder值一样,就继续执行。如果coder的值是LATIN1就执行StringLatin1.equals(value, aString.value)
,否则就执行StringUTF16.equals(value, aString.value)
。
注意这里的 StringLatin1.equals
方法说明了这里调用的equals方法被定义在StringLatin1类中(StringUTF16类对该方法的实现是一模一样的),源代码如下:
@HotSpotIntrinsicCandidate
public static boolean equals(byte[] value, byte[] other) {
if (value.length == other.length) {
for (int i = 0; i < value.length; i++) {
if (value[i] != other[i]) {
return false;
}
}
return true;
}
return false;
}
从上面源码可以知道,如果coder相等那么就将两个字符串的长度进行比较,如果长度相等就逐个对比两个字符串的字符并返回结果。
小结
String类对equals进行了重写,比较 x
和 y.toLowerCase()
的过程:
- 比较内存单元存储的内容是否相同,由于x和y内存单元中存储的内存地址不同,继续判断;
- 对象是否是String类型的实例,x和y都是String类型的实例,继续判断;
- 判断这两个字符串长度是否相等,这两个字符串长度相等,继续判断;
- 判断这两个字符串的每个字符是否相等,结果是相等,所以返回true。
3)StringBuffer
类与String无关,并没有对equals方法进行重写
StringBuffer类并没有对equals方法进行重写,查看可知调用的是Object原始的equals方法,比较的是内存单元的内容,内存单元存储的是两个字符串对象的 引用/内存地址,所以下面返回结果为false。
public static void main(String[] args) {
StringBuffer stringBuffer1 = new StringBuffer("hello");
StringBuffer stringBuffer2 = new StringBuffer("hello");
System.out.println(stringBuffer1.equals(stringBuffer2)); // false
}
他の者にできたか?ここまでやれたか?この先できるか?いいや、仆にしかできない!