在Java语言里,当使用双等号==比较两个引用类型的变量时,实际比较的是它们所保存的对象内存地址,也就是判断这两个变量是否指向堆中的同一个实例。这一机制与基本数据类型的值比较完全不同,也是很多初学者在对象相等性判断上容易踩坑的地方。

一、Java内存模型与引用比较的本质
要理解==对对象的比较行为,先要看Java的内存划分。局部变量表中的引用变量存放的是地址,而对象实例本身在堆中分配。当我们写Object a = new Object();时,a里存的是堆中那个Object实例的起始地址。
使用==比较引用类型,虚拟机直接比对这两个地址数值是否相等。如果相等,说明两个引用指向同一个对象;如果不等,哪怕两个对象内部状态一模一样,也只是不同的内存实体。这和equals方法默认实现不同,后者在不重写时虽然也用==,但语义上我们常把它视为内容比较的入口。
二、基础代码示例演示
下面通过一段简单代码展示独立对象与同一引用下的==结果差异。
public class RefCompareDemo {
public static void main(String[] args) {
// 两个独立new的对象,地址不同
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2); // false,地址不同
// 同一对象赋值给两个引用
String s3 = s1;
System.out.println(s1 == s3); // true,指向同一实例
// 字面量来自常量池,指向同一地址
String s4 = "hello";
String s5 = "hello";
System.out.println(s4 == s5); // true,常量池复用
}
}
从输出可以看到,new出来的两个String尽管内容都是hello,但==返回false。而字面量赋值因Java的字符串常量池机制,让s4和s5指向同一块内存,所以==为true。
这种差异提醒我们:若业务需判断字符串内容一致,必须用equals方法;若确实要确认是否为同一对象,才使用==。混淆二者会造成难以察觉的逻辑bug。
三、包装类型的缓存干扰
Integer等包装类在特定范围也会缓存对象,使得==出现看似违反直觉的结果。
public class IntegerCacheDemo {
public static void main(String[] args) {
Integer i1 = 127;
Integer i2 = 127;
System.out.println(i1 == i2); // true,命中缓存
Integer i3 = 128;
Integer i4 = 128;
System.out.println(i3 == i4); // false,超出缓存范围
}
}
Integer对-128到127的值做了池化,自动装箱时复用同一对象,因此小数值的==为true。超过范围则每次新建实例,==自然为false。这进一步说明==只认地址,不认数值。
在真实开发中,比较包装类数值应统一用equals或拆箱后比基本值,避免依赖缓存范围导致线上问题。
四、如何正确判断对象地址相同
如果需求本身就是确认两个引用是否指向同一对象,直接使用==即可,它是最快且语义明确的做法。
public class SameRefCheck {
static class Node {
int val;
Node(int v) { val = v; }
}
public static void main(String[] args) {
Node n1 = new Node(1);
Node n2 = n1;
Node n3 = new Node(1);
System.out.println(n1 == n2); // true
System.out.println(n1 == n3); // false
}
}
上述代码里n2由n1赋值,二者地址相同;n3是新实例,地址不同。这种写法在需要防止重复处理同一实体、或做浅拷贝判断时非常实用。
总结来说,双等号在Java引用比较中扮演地址裁判的角色。认清它和equals的分工,结合常量池与包装缓存现象,才能写出健壮的对象比较逻辑。