场景开始: 诡异的 == 判断
Integer a = 127;
Integer b = 127;
System.out.println(a == b); // true
Integer c = 128;
Integer d = 128;
System.out.println(c == d); // false???
如果你有疑惑,可以详细阅读本篇文章,了解Java 基本数据类型的那些“隐藏规则”。
一、Java 有哪 8 种基本数据类型?
先回顾下java的8种基本类型及对应的包装类
| 基本类型 | 包装类 | 占用字节 |
|---|---|---|
byte | Byte | 1 |
short | Short | 2 |
int | Integer | 4 |
long | Long | 8 |
float | Float | 4 |
double | Double | 8 |
char | Character | 2 |
boolean | Boolean | - |
二、类型转换陷阱
坑1:隐式类型转换(小转大)的意外
小范围向大范围转换时,可能因中间计算溢出导致结果错误。
int a1 = Integer.MAX_VALUE; // 2147483647
int b1 = a1 + 1;
System.out.println("b1="+b1);// 溢出变为 b1=-2147483648(整数溢出回绕)
long c1 = b1;
System.out.println("c1="+c1);// 结果为 c1=-2147483648(错误,因 b 已溢出)
避坑:先将其中一个操作数转为大范围类型再计算:
long c2 = (long)a1 + 1;
System.out.println("c2="+c2);// 正确结果:2147483648
坑2:显式类型转换(大转小)的精度丢失
大范围类型强制转为小范围类型时,直接丢弃高位字节,导致数据失真。
long a2 = 2147483648L; // 超出 int 最大值
int b2 = (int)a2;
System.out.println("b2="+b2);// 结果为 -2147483648(高位丢失,溢出回绕)
避坑:转换前先判断值是否在目标类型范围内:
if (a2 >= Integer.MIN_VALUE && a2 <= Integer.MAX_VALUE) {
int b22 = (int)a2;
} else {
// 处理溢出逻辑
System.out.println("超出Integer最大范围");
}
三、数值运算溢出
坑3:整数运算溢出
坑:int/long 运算结果超出范围时,不会报错,而是按 “模 2^n” 回绕(负数用补码表示)。
int a3 = 1100000000;
int b3 = 1100000000;
int c3 = a3 + b3;
System.out.println("c3="+c3);// 结果为 c3=-2094967296(溢出)
避坑:用更大范围类型(如 long)承接结果:
long c33 = (long) a3 + b3;
System.out.println("c33="+c33);// 正确结果:2200000000
坑4:浮点数相加,结果不可预测!
double a4 = 0.1;
double b4 = 0.2;
System.out.println(a4 + b4 == 0.3); // false!
原因:浮点数在二进制中无法精确表示(类似 1/3 在十进制中是 0.333...)。
避坑:浮点精确计算用 BigDecimal
//BigDecimal陷阱,0.1在内存中存储的是近似值,构造函数接收了这个不精确的值
BigDecimal b1 = new BigDecimal(0.1);
BigDecimal b2 = new BigDecimal(0.2);
System.out.println(b1.add(b2).doubleValue()==0.3);//false
//方式一:使用字符串构造函数(推荐)
BigDecimal b3 = new BigDecimal("0.1");
BigDecimal b4 = new BigDecimal("0.2");
System.out.println(b3.add(b4).doubleValue()==0.3); //true
//方式二:使用 valueOf 方法
BigDecimal b5 = BigDecimal.valueOf(0.1);
BigDecimal b6 = BigDecimal.valueOf(0.2);
System.out.println(b5.add(b6).doubleValue()==0.3); //true
四、自动装箱/拆箱的陷阱
坑5:包装类为null 拆箱会报空指针异常!
Integer x = null;
int y = x; // NullPointerException!
避坑:拆箱前先判断是否为 null。
int b55 = (a5 != null) ? a5 : 0;
坑6:== 比较包装类,结果不可预测!
回到开头的例子:
Integer a6 = 127;
Integer b6 = 127;
System.out.println(a6 == b6); // true
Integer a66 = 128;
Integer b66 = 128;
System.out.println(a66 == b66); // false
原因分析:Java 对 -128 ~ 127 的 Integer 做了缓存。编译器会在基本类型自动装箱过程调用 valueOf() 方法。
- 缓存池范围内从缓存中取,返回的是相同对象。
- 超出范围则每次新建对象,返回的是不同对象。
java21源码:
public static Integer valueOf(int i) {
//-128 ~ 127均取自IntegerCache,即同一个对象
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
//超出范围,new一个新对象
return new Integer(i);
}
private static final class IntegerCache {
static final int low = -128;
static final int high;
@Stable
static final Integer[] cache;
static Integer[] archivedCache;
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
h = Math.max(parseInt(integerCacheHighPropValue), 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(h, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
// Load IntegerCache.archivedCache from archive, if possible
CDS.initializeFromArchive(IntegerCache.class);
int size = (high - low) + 1;
// Use the archived cache if it exists and is large enough
if (archivedCache == null || size > archivedCache.length) {
Integer[] c = new Integer[size];
int j = low;
//重点看这里j初始值-128
for(int i = 0; i < c.length; i++) {
c[i] = new Integer(j++);
}
archivedCache = c;
}
//赋值给cache数组
cache = archivedCache;
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
避坑:比较值用 .equals(),而不是 ==或者!=
Integer a666 = 128;
Integer b666 = 128;
System.out.println(a666.equals(b666)); // true
扩展:Byte, Short, Long 也有缓存-128127,127,但 Character 缓存 0Float/Double 没有缓存(重复的精确值远少于整数,缓存带来的收益无法抵消成本)!
Byte:
public static Byte valueOf(byte b) {
final int offset = 128;
return ByteCache.cache[(int)b + offset];
}
Short
public static Short valueOf(short s) {
final int offset = 128;
int sAsInt = s;
if (sAsInt >= -128 && sAsInt <= 127) { // must cache
return ShortCache.cache[sAsInt + offset];
}
return new Short(s);
}
Long
public static Long valueOf(long l) {
final int offset = 128;
if (l >= -128 && l <= 127) { // will cache
return LongCache.cache[(int)l + offset];
}
return new Long(l);
}
Character
public static Character valueOf(char c) {
if (c <= 127) { // must cache
return CharacterCache.cache[(int)c];
}
return new Character(c);
}
private static final class CharacterCache {
private CharacterCache(){}
@Stable
static final Character[] cache;
static Character[] archivedCache;
static {
int size = 127 + 1;
// Load and use the archived cache if it exists
CDS.initializeFromArchive(CharacterCache.class);
if (archivedCache == null || archivedCache.length != size) {
Character[] c = new Character[size];
for (int i = 0; i < size; i++) {
//重点看这里
c[i] = new Character((char) i);
}
archivedCache = c;
}
cache = archivedCache;
}
}
Float
public static Float valueOf(float f) {
return new Float(f);
}
Double
public static Double valueOf(double d) {
return new Double(d);
}
Boolean
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
五、布尔类型的坑
坑7:布尔类型的隐式转换误区
Java 不允许 boolean 与其他基本类型(如 int)相互转换。
boolean a7 = true;
int b7 = (int)a7; // 编译报错(无法强制转换)
避坑:用三元运算符或 if-else 显式转换,避免直接类型转换。
int b77 = a7 ? 1 : 0; // 正确(三元运算符)
六、思考题
下面代码输出什么?
Integer e = new Integer(100);
Integer f = 100;
System.out.println(e == f); // ?
Boolean b8 = new Boolean(true);
Boolean b9 = new Boolean(true);
System.out.println(b8==b9);//?
欢迎在评论区留下你的答案!