在Java中使用“==”运算符比较包装对象

69

我正在阅读Kathy Sierra和Bert Bates的《SCJP Java 6》,但是这本书让我感到非常困惑。在第245页,他们列出了以下代码。

Integer i1 = 1000;
Integer i2 = 1000;
if(i1 != i2)
System.out.println("different objects");

//Prints output
different objects

然后在接下来的页面上,他们有以下代码

Integer i3 = 10;
Integer i4 = 10;
if(i3 == i4)
System.out.println("same objects");

//Prints output
same objects

我很困惑!当我自己尝试时,似乎不能像使用equals()方法一样使用"=="来进行比较。即使Integer变量设置为相同的值(例如10),使用"=="始终会给我返回'false'。我是正确的吗?使用"=="比较相同值的Integer对象将始终导致返回'false'


2
我认为这个链接可以帮助您:https://dev59.com/yHI_5IYBdhLWcg3wHvU5 - Tarik
1
为什么 Java 中的整数不被缓存? - Patrick
1
这个链接:https://dev59.com/nG435IYBdhLWcg3w9FBY 和这个链接:https://dev59.com/pmoy5IYBdhLWcg3wnfYi - Patrick
8个回答

77

答案的关键是被称为对象内部化的过程。Java会对小于128的数字进行内部化处理,所以所有在内部化范围内的Integer(n)实例都是相同的。大于或等于128的数字不会被内部化处理,因此Integer(1000)对象不相等。


2
哇!我也刚看到了。为什么??那太令人困惑了。这背后的原因是什么? - dido
12
请注意,仅从文字、自动装箱和 Integer.valueOf() 获取的对象是内部化对象,而使用 new Integer 构造的对象始终是不同的对象。 - ratchet freak
2
@dido 整数对象池化的原因之一是为了节省内存,同时在一定程度上也可以节省时间。当 Integer 对象用作哈希映射中的键或哈希集合中的对象时,小整数的百分比是不成比例地大的。通过对它们进行池化,您可以跳过重新分配内存以容纳相互等同的不可变对象的相同副本,并通过首先检查引用相等性来加快相等性比较的完成速度。最重要的是,这几乎不会消耗 CPU 周期,因此这是一个很容易做出的决定。 - Sergey Kalinichenko
Java不会对小数字进行内部化处理。Integer本身正在执行对象池化。 - Steve Kuo
@SteveKuo 您说得对 - 我宽泛地使用了这个术语,旨在指代Java编程语言及其库,特别是java.lang.Integer。两者几乎是不可分割的(例如,Java编译器“知道”如何将int常量装箱为Integers),因此我认为这种用法是合理的。 - Sergey Kalinichenko
显示剩余7条评论

21

如果你查看 Integer 的源代码,你会发现 Integer.valueOf(int) 将所有的值在-128到127之间池化(pools)。原因是小的 Integer 值经常被使用,因此值得被池化/缓存。

直接从 Integer.java 中提取:

public static Integer valueOf(int i) {
    if(i >= -128 && i <= IntegerCache.high)
        return IntegerCache.cache[i + 128];
    else
        return new Integer(i);
}
请注意,此池化是实现特定的,没有对池化范围的保证。
关于字符串常量池的回答在概念上是正确的,但术语使用不正确。在 Java 中,通常暗示着 Java 运行时执行了池化(如 String 的 intern)。在 Integer 的情况下,是类本身在执行池化。这里没有涉及 JVM 魔法。

1
实际上,在API文档中指定了在范围[-128, 127]内的int值的Integer对象的缓存,因此该范围的部分是确保的。 - Ted Hopp

7
上面关于实习的回答是正确的。但如果你要做实习,需要考虑一些事情:
Integer i3 = new Integer(10);
Integer i4 = new Integer(10);

如果您明确地创建了新对象,则不会拥有新对象。如果您按照以下方式编写代码,它将被整合:

Integer i3 = Integer.valueOf(10);
Integer i4 = Integer.valueOf(10);

现在它们将再次成为同一个对象。如果您查看src.zip文件中Integer.java类中的valueOf方法,可以看到它检查int值是否超出-128至127范围,如果是,则调用新的Integer类;否则从缓存中加载。


3
Integer i1 = 1000;
Integer i2 = 1000;

编译器将整数1000“装箱”为Integer对象。它会将源代码转换为以下内容:
Integer i1 = Integer.valueOf(1000);
Integer i2 = Integer.valueOf(1000);

现在,valueOf 可以简单调用 new Integer(1000),但是每次装箱 int 时创建一个新的 Integer 对象会耗费时间和空间。为了避免这种情况,Integer 类保留了一个有限范围内 int 值的 Integer 对象数组。

if(value> maxRange || value< minRange){
     //not in pool return new Integer
     return new Integer(value);
}else{
     //return pooled Integer object
     //for the value, pool contains all Integer
     //values from minRange to maxRange
     return integerPool[value-minRange];
}

可以通过在程序启动时使用JVM参数设置范围(默认为-127到128)来调整由此得到的速度与内存的损失。


0
根据 jls-5.1.7
If the value p being boxed is true, false, a byte, or a char in the range \u0000 to \u007f,   
or an int or short number between -128 and 127 (inclusive), then let r1 and r2 
be the results of any two boxing conversions of p. It is always the case that r1 == r2.

因此,Integer类会缓存介于-128127 之间的任何数字。
请记住,在比较两个对象时,请始终使用equals方法。

缓存代码编写在IntegerCache类中,它是Integer类的成员。

以下是代码片段:

 /**
 * Cache to support the object identity semantics of autoboxing for values between
 * -128 and 127 (inclusive) as required by JLS.
 *
 * The cache is initialized on first usage.  The size of the cache
 * may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
 * During VM initialization, java.lang.Integer.IntegerCache.high property
 * may be set and saved in the private system properties in the
 * sun.misc.VM class.
 */

private static class IntegerCache {
    static final int low = -128;
    static final int high;
    static final Integer cache[];

    static {
        // high value may be configured by property
        int 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_VALUE
                h = 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() {}
}

参考资料


0

"==" 操作符总是比较值的内存位置或对象引用。 equals 方法总是比较值。但 equals 方法也间接使用 "==" 操作符进行值的比较。Integer 使用 Integer 缓存来存储从 -128 到 +127 的值。如果使用 "==" 运算符检查任何在 -128 到 127 之间的值,则会返回 true。如果任何值在 -128 到 127 之间,则为

Integer i1 = -128; 
Integer i2 = -128; 
System.out.println(i1 == i2); // returns true

如果超出上述范围,则返回false。

Integer i1 = 1000;
Integer i2 = 1000;
System.out.println(i1 == i2); // returns false

请参考此链接以获取更多信息


0

使用 == 和 != 进行字符串比较和整数比较会产生布尔结果,与我们期望的不同。因此,请小心并确保可能的未知结果不会影响您软件的性能、可靠性和准确性。


0
当Java中使用"=="运算符来比较非基本类型的任何内容时,它会检查引用相等性;即使被比较的对象是包装的基本类型也是如此。此外,valueOf方法和编译器生成的自动装箱语句通常可以任意返回一个新对象,该对象与先前存在的任何其他引用都不具有引用相等性,或者返回对现有对象的引用(当然,这种引用将与标识同一对象的任何先前存在的引用具有引用相等性)。实现必须维护一个池,其中包含数值-128到127之间的Integer实例,以便在该范围内对任何特定数字调用Integer.valueOf时都返回对同一对象的引用,但除此之外,实现可自由进行类似以下的操作:
static Integer [] intPool = new Integer[256];

public Integer valueOf(int n)
{
  int hash = (n*0x18675309) >>> 24;
  Integer instance = intPool[n];
  if (instance == null && instance.value != n)
  {
    instance = new Integer(n);
    intPool[hash] = instance ;
  }
  return instance;
}

我并不特别期望Java实现会做这样的事情,因为在许多情况下,“缓存命中”比率可能接近0%,在缓存中查找实例所花费的额外时间将被浪费。尽管如此,永远没有任何保证由instanceOf返回的引用不会匹配该方法返回的某个先前引用(即使它不匹配该方法返回的最后一个引用,某些缓存算法可能会导致它返回一个更早的引用,特别是如果池由多个线程共享而没有锁定)。缺乏锁定永远不会导致代码返回除具有正确值的整数引用之外的任何内容,但可能会导致返回的引用的比较相等的不可预测变化。只有使用构造函数new Integer(n)直接创建的Integer对象的引用是唯一的;期望valueOf返回的任何引用都不与valueOf返回的任何引用匹配(而没有实际观察到它不匹配)的代码应被视为错误。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接