128陷阱
- 128陷阱的概念
- 包装器类
- 自动装箱
- 自动拆箱
- 128陷阱
- Intager源码
- equals
128陷阱的概念
首先想要清楚什么是128陷阱,需要了解一些概念
包装器类
包装器类(Wrapper classes)是Java中的一组类,它们允许将基本数据类型(如int、char、boolean等)封装为对象。这些包装器类提供了许多有用的方法,用于在基本数据类型和对象之间进行转换和操作。这里给出两个例子
基本类型 | 包装器类 |
---|---|
int | Integer |
double | Double |
自动装箱
自动装箱(Autoboxing)是Java中的一种特性,它允许在基本数据类型和对应的包装器类之间进行自动的转换。当你使用基本数据类型的值赋给一个对应的包装器类对象时,编译器会自动将其转换为包装器类对象。
当把一个int类型的值赋给一个Integer对象时,自动装箱就会发生:
int number = 42; // 基本数据类型
Integer wrappedNumber = number; // 自动装箱
System.out.println("Wrapped Number: " + wrappedNumber);
上述代码将int类型的值42赋给一个Integer对象wrappedNumber,编译器会自动将其转换为Integer对象。这样,我们就可以像操作对象一样操作基本数据类型
自动拆箱
自动拆箱(Unboxing)是Java中的另一个特性,它与自动装箱相反。自动拆箱允许将包装器类对象自动转换为对应的基本数据类型。
当你将一个包装器类对象赋给一个基本数据类型变量时,编译器会自动进行拆箱操作。
Integer wrappedNumber = Integer.valueOf(42); // 创建一个Integer对象
int unwrappedNumber = wrappedNumber; // 自动拆箱
System.out.println("Unwrapped Number: " + unwrappedNumber);
128陷阱
在Java中,对于范围在-128到127之间的整数值,自动装箱后的包装器类对象会被缓存起来以提高性能。这意味着对于这个范围内的整数值,每次装箱得到的包装器类对象都是同一个对象。
然而,当我们超过这个范围时,就会遇到所谓的"128陷阱"。这是因为超过范围的整数值会导致自动装箱时创建新的包装器类对象,而不是使用缓存中的对象。
Integer num1 = 100;
Integer num2 = 100;
System.out.println(num1 == num2); // 输出: trueInteger num3 = 200;
Integer num4 = 200;
System.out.println(num3 == num4); // 输出: false
Intager源码
我们从源码的角度来分析一下为什么会产生128陷阱
private static class IntegerCache {static final int low = -128;static final int high;static final Integer cache[];static {// high value may be configured by propertyint h = 127;String integerCacheHighPropValue =sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");if (integerCacheHighPropValue != null) {try {int i = parseInt(integerCacheHighPropValue);i = Math.max(i, 127);// Maximum array size is Integer.MAX_VALUEh = Math.min(i, Integer.MAX_VALUE - (-low) -1);} catch( NumberFormatException nfe) {// If the property cannot be parsed into an int, ignore it.}}high = h;cache = new Integer[(high - low) + 1];int j = low;for(int k = 0; k < cache.length; k++)cache[k] = new Integer(j++);// range [-128, 127] must be interned (JLS7 5.1.7)assert IntegerCache.high >= 127;}private IntegerCache() {}}
首先,这个类定义了两个静态常量low和high,low的值为-128,high的值在静态初始化块中被设置为127。这两个常量定义了缓存的整数值的范围,即[-128, 127]。
静态初始化块中的代码首先尝试通过读取系统 java.lang.Integer.IntegerCache.high来获取用户配置的缓存上限值。如果该属性存在且能够被解析为一个整数值,将该值与127进行比较,并确保不超过整数数组的最大容量。接下来,代码创建了一个大小为(high - low) + 1的整数数组cache,用于存储缓存的整数对象。使用low的初始值,从low递增到high的范围,通过循环创建了缓存数组中的每个整数对象。最后,通过断言(assert)语句,确保high的值至少为127,以满足Java语言规范(JLS 7 5.1.7)对范围[-128, 127]的整数值必须被缓存的要求。
当使用一个超过范围的整数值进行自动装箱时,循环创建缓存数组cache的过程会创建一个新的整数对象,而不是使用缓存中的对象。这就是为什么超过范围的整数值会导致自动装箱后产生新的对象的原因。
equals
当我们调用equals()方法时,它会在包装器类中执行特定的逻辑来比较两个对象的值是否相等。为了实现这个逻辑,包装器类会重写equals()方法。
在Java中,equals()方法的默认实现是比较对象的引用,即使用==运算符。但是,包装器类(如Integer、Double等)对equals()方法进行了重写,以便比较它们所包装的值是否相等。
当我们调用equals()方法时,它会首先检查传入的对象是否为同一类型的包装器类对象。如果不是,它会立即返回false,因为不同类型的对象不可能具有相等的值。
如果传入的对象是同一类型的包装器类对象,equals()方法会进一步比较两个对象所包装的值。这种比较是按照数值的逻辑进行的,而不是比较引用。
例如,当我们调用num3.equals(num4)时,equals()方法会比较num3和num4所包装的整数值。如果这两个值相等,equals()方法将返回true;否则,它将返回false。
这样,通过重写equals()方法,包装器类可以实现对值的比较,从而正确判断两个对象是否相等。