在Java中判断两个变量是否相等,并不能简单地套用一种写法。根据变量是基础类型还是引用类型,相等比较的行为有本质区别,同时还会受到自动装箱、对象方法重写等因素影响。只有理清这些规则,才能避免在业务逻辑里写出隐蔽的判等bug。

一、基础类型的==比较
Java里的八种基础类型(byte、short、int、long、float、double、char、boolean)直接存储数值本身。当使用==运算符比较两个基础类型变量时,比较的是它们实际存储的二进制值是否相同,不涉及任何对象地址概念。
例如两个int变量,只要数值一样,==就返回true。即使一个是计算得出的,一个是字面量声明的,结果也一样。下面的代码演示了基础类型的判等行为:
public class PrimitiveCompare {
public static void main(String[] args) {
int a = 10;
int b = 5 + 5;
char c = 'A';
char d = 65; // ASCII中65就是'A'
System.out.println(a == b); // true,值相等
System.out.println(c == d); // true,char本质也是数值
double x = 0.1 + 0.2;
double y = 0.3;
System.out.println(x == y); // false,浮点精度误差导致
}
}
需要注意的是,浮点类型由于二进制表示限制,0.1加0.2并不严格等于0.3,所以用==比较浮点值经常失败。业务里如果必须比较浮点数,通常要取两者差值的绝对值小于某个极小量(如1e-9)来判定。
基础类型的比较不存在空指针问题,也不会调用任何方法,因此是最简单也最安全的相等判断方式,前提是你的数据确实是基础类型而非包装类。
二、引用类型的==与equals区别
引用类型变量存储的是对象在堆内存中的地址。当你用==比较两个引用类型变量时,Java只检查它们是否指向同一个对象实例,而不是检查对象内容是否相同。这种比较常被称为“地址比较”。
如果要比较对象的内容,应当使用equals方法。equals是定义在Object类里的实例方法,默认实现其实就是return this == obj;,但很多类(如String、Integer、List)都重写了它,改为比较内部字段。看下面这段对比代码:
public class ReferenceCompare {
public static void main(String[] args) {
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2); // false,两个不同对象
System.out.println(s1.equals(s2)); // true,内容相同
Object o1 = new Object();
Object o2 = new Object();
System.out.println(o1.equals(o2)); // false,Object未重写,等价于==
}
}
从输出可以看出,即便字符串内容完全一样,只要是两个new出来的对象,==就返回false。而equals因为被String重写过,能够识别字符序列一致。如果自定义类没有重写equals,那么即使你写了逻辑上的“相等”,调用equals也只会走Object的默认地址比较。
因此写业务类时,若需要按内容判等,必须同时重写equals和hashCode,否则将该对象放入HashMap或HashSet时会出现逻辑错误,因为集合先靠hashCode定位桶,再用equals确认。
三、包装类型的缓存陷阱
Java为部分包装类设计了对象缓存,以节省内存和提升性能。最典型的是Integer,在自动装箱(如Integer i = 100;)时,如果数值落在-128到127之间,会复用同一个缓存对象;超出这个范围才会真正new一个对象。
这一机制导致用==比较Integer可能出现“时灵时不灵”的现象。下面的例子展示了该陷阱:
public class IntegerCacheDemo {
public static void main(String[] args) {
Integer a = 127;
Integer b = 127;
System.out.println(a == b); // true,命中缓存,同一对象
Integer c = 128;
Integer d = 128;
System.out.println(c == d); // false,超出缓存,不同对象
System.out.println(c.equals(d)); // true,内容比较安全
}
}
很多开发者在测试环境用小数调试通过,上线后遇到大数值就莫名判等失败,根源就在这里。Byte、Short、Long也有类似的固定缓存区间,Character缓存的是0到127的字符。
所以任何包装类比较,都应该使用equals,或者先调用intValue()之类的方法转回基础类型再用==。不要依赖自动装箱的缓存行为来写相等判断。
四、避免空指针的判等写法
引用类型调用equals时,如果左边对象是null,会直接抛出NullPointerException。在真实业务里,变量可能来自接口参数或数据库查询,未必有值。
一种稳健的写法是把必定非空的常量放在equals前面,或者使用JDK提供的Objects.equals工具方法。示例如下:
import java.util.Objects;
public class NullSafeCompare {
public static void main(String[] args) {
String input = null;
// 常量放前面,安全
System.out.println("hello".equals(input)); // false,不抛异常
// 使用工具类,两边都可空
System.out.println(Objects.equals(input, "hello")); // false
System.out.println(Objects.equals(null, null)); // true
}
}
Objects.equals内部做了空判断:如果两个都为null返回true;如果一个为null另一个非null返回false;都不为null才调用第一个参数的equals。这样能大幅降低判等代码的防御性编写成本。
在代码评审中,应当统一团队规范:所有对象判等优先用Objects.equals,或明确常量在前,禁止出现未知对象直接点equals的写法。
五、常见比较规则速查表
为方便记忆,下面用表格总结不同场景下的推荐比较方式:
| 变量类型 | 比较目的 | 推荐写法 | 风险点 |
|---|---|---|---|
| int、double等基础类型 | 值相等 | 使用== | 浮点精度不可用==直接比 |
| String | 内容相等 | Objects.equals(a,b) | 用==比的是地址 |
| Integer等包装类 | 数值相等 | equals或intValue后== | 缓存区间导致==不稳定 |
| 自定义对象 | 业务相等 | 重写equals+hashCode后用equals | 未重写则比地址 |
掌握上表之后,面对绝大多数Java相等判断需求都不会再犹豫。核心原则只有一句:基础类型比值用==,引用类型比内容用equals且防null。
六、总结与实践建议
Java的相等比较之所以容易出错,是因为它把“值比较”和“对象身份比较”分得很清。编译器不会阻止你用==去比两个字符串,但运行结果往往违背直觉。
建议在项目基础包里封装一个比较工具,统一收敛判等逻辑;对实体类用IDE自动生成equals和hashCode,并选用业务逻辑主键参与计算。只要团队形成统一习惯,比较运算带来的线上问题基本可以归零。