前言
从这一篇开始,我们进入 Java 面试八股系列。
这一系列不只适合面试前背题,也适合用来补 Java 基础。很多问题看起来简单,但是如果只会一句"== 比较地址,equals 比较内容",面试时很容易被继续追问。
比如面试官可能会问:
==和equals有什么区别?String使用==比较为什么可能是true?equals默认比较的是什么?- 为什么重写
equals时通常也要重写hashCode? - 基本数据类型能不能调用
equals?
这一篇我们就把 == 和 equals 从基础到面试回答完整梳理一遍。
一、先给结论
可以先记住一句话:
==比较的是值。对于基本数据类型,比较的是具体数值;对于引用数据类型,比较的是对象地址。
equals是 Object 类中的方法,默认比较对象地址,但很多类会重写它,用来比较对象内容。
这句话里面有几个重点:
==是运算符equals是方法- 基本数据类型没有
equals - 引用类型使用
==比较地址 equals默认也比较地址String、Integer等类重写了equals
下面逐个来看。
二、== 比较基本数据类型
对于基本数据类型,== 比较的是具体的值。
比如:
java
public class Test {
public static void main(String[] args) {
int a = 10;
int b = 10;
System.out.println(a == b);
}
}
输出结果:
text
true
原因很简单,a 和 b 都是 int 类型,并且值都是 10。
再看一个例子:
java
public class Test {
public static void main(String[] args) {
char c = 'A';
int num = 65;
System.out.println(c == num);
}
}
输出结果:
text
true
因为字符 'A' 对应的编码值就是 65,所以比较结果为 true。
这里说明一点:
基本数据类型使用
==时,比较的是变量中保存的具体值。
常见基本数据类型包括:
text
byte
short
int
long
float
double
char
boolean
它们不是对象,所以不能直接调用 equals 方法。
三、== 比较引用数据类型
引用数据类型使用 == 时,比较的是两个引用变量保存的地址是否相同。
先看一个例子:
java
public class Test {
public static void main(String[] args) {
Student s1 = new Student("张三");
Student s2 = new Student("张三");
System.out.println(s1 == s2);
}
}
class Student {
private String name;
public Student(String name) {
this.name = name;
}
}
输出结果:
text
false
虽然两个学生的姓名都是 张三,但是它们是通过两次 new 创建出来的两个对象。
java
Student s1 = new Student("张三");
Student s2 = new Student("张三");
这两个对象在堆内存中的地址不同,所以:
java
s1 == s2
结果是 false。
再看另一个例子:
java
public class Test {
public static void main(String[] args) {
Student s1 = new Student("张三");
Student s2 = s1;
System.out.println(s1 == s2);
}
}
输出结果:
text
true
因为:
java
Student s2 = s1;
这行代码不是创建新对象,而是让 s2 指向 s1 指向的同一个对象。
所以 s1 和 s2 保存的对象地址相同,比较结果就是 true。
四、equals 默认比较什么?
equals 是 Object 类中的方法。
所有 Java 类都直接或间接继承自 Object,所以所有对象都可以调用 equals 方法。
Object 类中 equals 的默认逻辑可以简单理解为:
java
public boolean equals(Object obj) {
return this == obj;
}
也就是说,如果一个类没有重写 equals 方法,那么它默认还是比较对象地址。
比如:
java
public class Test {
public static void main(String[] args) {
Student s1 = new Student("张三");
Student s2 = new Student("张三");
System.out.println(s1.equals(s2));
}
}
class Student {
private String name;
public Student(String name) {
this.name = name;
}
}
输出结果:
text
false
原因是 Student 类没有重写 equals,所以调用的是 Object 默认的 equals 方法,本质上还是比较地址。
五、String 中的 equals 为什么比较内容?
String 类重写了 equals 方法,所以它比较的是字符串内容。
比如:
java
public class Test {
public static void main(String[] args) {
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));
}
}
输出结果:
text
false
true
解释一下:
java
s1 == s2
比较的是两个 String 对象的地址。因为它们是两次 new 出来的对象,所以地址不同,结果是 false。
java
s1.equals(s2)
比较的是字符串内容。两个字符串内容都是 hello,所以结果是 true。
这就是面试中最常见的一组对比。
六、String 使用 == 为什么有时是 true?
看下面这个例子:
java
public class Test {
public static void main(String[] args) {
String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2);
}
}
输出结果:
text
true
为什么这里 == 是 true?
因为字符串字面量会放入字符串常量池。
java
String s1 = "hello";
String s2 = "hello";
第一次出现 "hello" 时,会在字符串常量池中创建这个字符串。
第二次再写 "hello" 时,会直接复用常量池中已有的字符串对象。
所以 s1 和 s2 指向的是同一个对象,使用 == 比较结果就是 true。
但是如果写成:
java
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2);
输出就是:
text
false
因为 new String("hello") 会在堆中创建新的字符串对象。
七、自己定义类时如何重写 equals?
如果我们希望两个 Student 对象只要姓名相同,就认为它们相等,可以重写 equals。
java
import java.util.Objects;
public class Student {
private String name;
private Integer age;
public Student(String name, Integer age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Student student = (Student) o;
return Objects.equals(name, student.name)
&& Objects.equals(age, student.age);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
测试代码:
java
public class Test {
public static void main(String[] args) {
Student s1 = new Student("张三", 18);
Student s2 = new Student("张三", 18);
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));
}
}
输出结果:
text
false
true
说明:
java
s1 == s2
依旧比较地址,所以是 false。
java
s1.equals(s2)
调用的是我们重写后的 equals 方法,比较的是姓名和年龄,所以是 true。
八、为什么重写 equals 通常也要重写 hashCode?
这是面试中很容易追问的点。
简单来说:
如果两个对象通过
equals判断相等,那么它们的hashCode也应该相等。
这和 HashMap、HashSet 这类集合有关。
比如我们把两个内容相同的 Student 放入 HashSet:
java
Set<Student> set = new HashSet<>();
set.add(new Student("张三", 18));
set.add(new Student("张三", 18));
System.out.println(set.size());
如果只重写 equals,不重写 hashCode,可能会导致集合认为它们是两个不同对象。
因为 HashSet 判断元素是否重复时,不只是看 equals,还会用到 hashCode。
所以实际开发中,只要重写 equals,通常就要一起重写 hashCode。
现在很多 IDE 都可以自动生成这两个方法。
九、基本数据类型能不能调用 equals?
不能。
比如:
java
int a = 10;
int b = 10;
System.out.println(a.equals(b));
这是错误写法。
因为 int 是基本数据类型,不是对象,没有方法。
如果是包装类,就可以调用 equals:
java
Integer a = 10;
Integer b = 10;
System.out.println(a.equals(b));
输出结果:
text
true
这里的 Integer 是包装类,是对象,所以可以调用 equals。
不过包装类还涉及自动装箱、缓存机制等问题,后面可以单独讲。
十、常见面试回答
如果面试官问:
==和equals有什么区别?
可以这样回答:
== 是运算符,equals 是方法。
如果比较的是基本数据类型,== 比较的是具体的值。
如果比较的是引用数据类型,== 比较的是两个对象的内存地址,也就是是否指向同一个对象。
equals 是 Object 类中的方法,默认实现和 == 一样,也是比较地址。但是很多类会重写 equals,比如 String、Integer,它们重写后通常比较的是对象内容。
所以在比较字符串内容时,应该使用 equals,而不是使用 ==。
十一、常见问题总结
1. String 比较内容应该用什么?
应该用:
java
s1.equals(s2)
如果担心空指针,可以写成:
java
"hello".equals(s1)
这样即使 s1 是 null,也不会报空指针异常。
2. equals 一定比较内容吗?
不一定。
如果类没有重写 equals,默认还是比较地址。
只有重写了 equals,才可能按照内容比较。
3. == 一定比较地址吗?
不一定。
如果是基本数据类型,== 比较的是值。
如果是引用数据类型,== 比较的是地址。
4. 为什么两个 new String 使用 == 是 false?
因为它们是两个不同对象,地址不同。
java
new String("hello") == new String("hello")
比较的是两个对象地址,所以是 false。
5. 重写 equals 必须重写 hashCode 吗?
语法上不是必须,但是强烈建议一起重写。
否则在 HashMap、HashSet 这类集合中可能出现问题。
十二、总结
这一篇主要学习了 == 和 equals 的区别。
== 是运算符。对于基本数据类型,它比较的是具体值;对于引用数据类型,它比较的是对象地址。
equals 是 Object 类中的方法,默认也是比较地址。但是像 String、Integer 这些类重写了 equals,所以它们可以用来比较内容。
面试时不要只背一句"== 比较地址,equals 比较内容",这句话并不完整。更准确的说法应该区分基本数据类型、引用数据类型,以及类有没有重写 equals。
下一篇我们继续学习 Java 中非常经典的问题:String 为什么是不可变的。