在了解==和equals之前, 我们先要了解java基本类型和引用类型的概念:
基本类型
java中一共分为8种基本数据类型:byte、short、int、long、float、double、char、boolean,其中byte、short、int、long是整型。float、double是浮点型,char是字符型,boolean是布尔型。
引用类型
我们可以这么理解, java中除去基本类型, 就是引用类型, 就像 Object obj = new Object(); 我们new出一个对象, 然后obj指向了它, obj就是引用类型变量. 上面讲述的8种基本类型, 它们对应着有8种封装类型, 如Integer, 它们也都是引用类型.
图片来源 深入理解Java虚拟机 p49
栈中存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型, 它存储的直接就是对象地址). 上图有个小细节, double占了两个格子, 那是因为64位长度的long和double类型的数据会占用2个局部变量, 其余的数据类型只占用1个.
接下来我们正式来比较==和equals的用法
==
说在前面: ==是关系操作符, 生成的是一个boolean结果, 它们计算的是操作数的值之间的关系.
我们先看下面的代码
public static void main(String[] args) {
Integer i = new Integer(7);
Integer j = new Integer(7);
int k = 7;
int m = 7;
System.out.println(k == m); //true
System.out.println(i == j); //false
System.out.println(i == k); //true
}
这段代码一共包含了三种情况,我们来一一比较.
一、基本类型 == 基本类型
两个基本类型变量的比较, 比较的是他们的数值, 所以为true
二、引用类型 == 引用类型
两个引用类型变量的比较, 比较的是它们的地址, 也就是它们是否指向同一个对象, 如果指向的是同一个对象, 为true,否则为false. i和j各自都在堆中new出了各自的对象, 所以它们的比较为false
三、基本类型 == 引用类型
也就是基本类型变量和它的封装类型变量比较, 封装类型将会自动拆箱变为基本类型后再进行比较, 也就是两个基本类型变量进行比较, 比较的是它们的数值, 所以为true
可能遇到面试的踩坑点:
public class Main {
public static void main(String[] args) throws InterruptedException {
Integer i = 123;
Integer j = 123;
System.out.println(i == j); // true
Integer a = 321;
Integer b = 321;
System.out.println(a == b); // false
}
}
这里要求你了解java的自动装箱还有是否看过Integer的源码
i和j会被自动装箱, 所有Integer i = 123 等同于 Integer i = Integer.valueOf(123), 我们一起来看看valueOf的源码:
我们可以看到, 代码并不会直接return一个new Integer(i), 而是会先经过一层判断, 我们来看看 IntegerCache, 它是Integer的私有静态内部类
我们可以看到默认的缓存最小值low是-128, 最大值high是127
我们再来看上面的程序 i和j的值是小于127的, 所以它们指向的是同一个对象, 而a和b是大于127的, 它们都是new Integer(i)创建了新的对象, 所以会返回false
小结: 带上基本类型变量的==比较, 将会比较它们的数值; 两个引用类型变量的比较, 会比较它们的地址.
要了解封装类型的自动装箱缓存.
equals
equals方法是基类Object中的方法, 我们先来看下Object中的写法
我们可以看到, 源码中的equals比较方法也是用==实现, 也就是比较的它们的地址, 如果指向的是同一个对象, 为true, 否则为false. 我们一起用测试代码来观察一下 :
public class Person {
private String name;
private Integer age;
public Person(String name, Integer age) {
this.age = age;
this.name = name;
}
public static void main(String[] args) {
Person p1 = new Person("test", 24);
Person p2 = new Person("test", 24);
System.out.println(p1.equals(p2));// false
}
}
当我们定义Person这么一个实体类的时候, 我们希望p1和p2的equals返回的结果是true, 因为我们传入了相同的参数, 但是事实是p1和p2指向了堆中各自的两个对象, 用Object的equals只能是返回false, 那怎样才能如我们所想返回true呢, 方法就是重写Person的equals方法(下面的代码我隐去了get和set方法,小伙伴们自己敲的时候记得加上):
public class Person {
private String name;
private Integer age;
public Person(String name, Integer age) {
this.age = age;
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
if (name != null ? !name.equals(person.name) : person.name != null) return false;
return age != null ? age.equals(person.age) : person.age == null;
}
public static void main(String[] args) {
Person p1 = new Person("test", 24);
Person p2 = new Person("test", 24);
System.out.println(p1.equals(p2));// true
}
}
我们在Person类中定义了自己的equals方法, 就是当类型相同并且姓名和年龄相同时, 我们就返回true, 代表相同. 我们可以在生产环境中根据实际情况自己定义equals的逻辑. 细心的小伙伴可能发现我们在equals方法里也用了name.equals和age.equals , 那这些equals就不用重写吗? no , 这是因为String类和Integer类自己重写了equals方法, 有兴趣的小伙伴可以自己看看这些重写的equals方法.
注意: 如果你重写了equals方法, 那么你也需要重写hashCode方法
总结: 在==和equals上, 我们只要理解基本类型和引用类型的区别, 就能更好的理解这两者的区别
参考
- 深入理解Java虚拟机 第二章
- Java编程思想 P.44
- 浅析Java基础类型与封装类型的区别
- Java中九种基本数据类型以及他们的封装类
- java基本类型与引用类型
- Java-从堆栈常量池解析equals()与==