如何在Java中正确比较两个整数?

304

我知道如果你将一个包装好的基本类型整数与一个常量进行比较,例如:

Integer a = 4;
if (a < 5)

a会自动拆箱,然后比较操作就可以正常运行。

但是,当你要比较两个封装的Integer对象时,想要比较相等性或大小关系怎么办呢?

Integer a = 4;
Integer b = 5;

if (a == b)

上面的代码会检查它们是否是同一个对象,还是在那种情况下自动拆箱?

那么下面的代码呢:

Integer a = 4;
Integer b = 5;

if (a < b)

?


48
一个明确的实验只能证伪而不能证明拆箱发生了。如果使用 == 而不是 equals 得到了正确的结果,这可能是因为装箱的数字被内部化或以其他方式被重复使用(作为编译器优化,可能是原因)。提问这个问题的原因是为了找出内部发生了什么,而不是看起来发生了什么。(至少我在这里的目的就是这样。) - Jim Pivarski
3
抱怨。Java 中最愚蠢的事情是无法重载运算符,例如 == 和 <,以执行一些明智的操作,例如处理 String 和 Integer 类型。因此,您必须使用 a.equals(b) 或 b.equals(a)。如果您想要处理 null(正如您应该!),则必须使用 Objects.equals(a,b)。 - John Henckel
我尝试了 Integer a = 4,Integer b = 4; a==b 返回 false。我不得不使用 if (x.intValue() == y.intValue())。 - Varun
10个回答

405
不,Integer、Long等之间的==会检查引用相等性,即:
Integer x = ...;
Integer y = ...;

System.out.println(x == y);

这将检查xy是否引用同一个对象,而不是相等的对象。

因此,

Integer x = new Integer(10);
Integer y = new Integer(10);

System.out.println(x == y);

保证会打印出false。对于“小”的自动装箱值的整理可能会导致棘手的结果:

Integer x = 10;
Integer y = 10;

System.out.println(x == y);

由于装箱的规则(JLS第5.1.7节),这将打印出true。仍然使用引用相等性,但实际上这些引用是相等的。

如果被装箱的值p是int类型的整数字面量,在-128和127之间(§3.10.1),或者是布尔字面值true或false(§3.10.3),或者是在'\u0000'和'\u007f'之间的字符字面量(§3.10.4),那么让a和b成为p的任意两个装箱转换的结果。总有一个情况是a == b。

个人建议使用:

if (x.intValue() == y.intValue())

或者

if (x.equals(y))

正如您所说,对于包装器类型(IntegerLong等)和数值类型(intlong等)之间的任何比较,包装器类型的值会被解包,并应用于涉及的原始值。这是作为二进制数推广的一部分发生的(JLS section 5.6.2)。查看每个单独运算符的文档,以查看是否应用了它。例如,从 ==!= 的文档中可以看到 (JLS 15.21.1):

如果相等操作符的操作数都是数值类型,或者其中一个是数值类型并且另一个可转换(§5.1.8)为数值类型,则在操作数上执行二进制数字提升(§5.6.2)。

而对于 <<=>>= (JLS 15.20.1)

每个数值比较运算符的操作数类型必须是可转换(§5.1.8)为原始数字类型的类型,否则将出现编译时错误。在操作数(§5.6.2)上执行二进制数值提升。如果操作数的提升类型为int或long,则执行有符号整数比较;如果此提升类型为float或double,则执行浮点比较。请注意,所有这些都不被视为“两种类型都不是数字类型”的情况的一部分。

4
为什么有些人会选择写x.compareTo(y) < 0而不是x < y呢? - Max Nanasy
2
@MaxNanasy:我目前想不到有什么。 - Jon Skeet
2
从Java 1.6.27+开始,在Integer类中有一个equals的重载,因此它应该与调用.intValue()一样高效。它将值作为原始int进行比较。 - otterslide
2
@Axel:添加重载不会改变“==”运算符的行为,对吧?我现在没有测试的条件,但如果这样做已经改变了,我会非常惊讶。 - Jon Skeet
@JonSkeet 这是关于“后者略微不太高效 - 没有Integer.equals(Integer)的重载”。 - Alex78191
显示剩余2条评论

49

==仍然会测试对象相等性。 然而,很容易被欺骗:

Integer a = 10;
Integer b = 10;

System.out.println(a == b); //prints true

Integer c = new Integer(10);
Integer d = new Integer(10);

System.out.println(c == d); //prints false

你使用不等式的例子是有效的,因为它们没有定义对象。然而,对于==比较,仍会检查对象的相等性。在这种情况下,当你从一个封装的基本类型初始化对象时,会使用同一个对象(用于a和b)。这是可以接受的优化,因为基本类型的封装类是不可变的。


15
如果您将数字文字更改为“200”,两个测试都将打印出“false”。 - Daniel Earwicker
2
在大多数JVM实现中,那是这样的。根据语言规范,结果可能因实现而异。 - Daniel Earwicker
5
我认为更清晰的称呼应该是"引用相等性(reference equality)",这样就很明显你的意思了。通常我会理解"对象相等性(object equality)"是指调用equals方法后的结果。 - Jon Skeet
但是 Integer a = 3000; Integer b = 3000;System.out.println(a == b); //输出 false - Danil Chernokalov
@AdamLewis,你能否发给我一个链接,解释一下如何让颜色像你那样工作呢?:)非常感谢! - Setup
显示剩余6条评论

48
自Java 1.7版本开始,您可以使用Objects.equals方法:
java.util.Objects.equals(oneInteger, anotherInteger);
返回true,如果这两个参数相等,否则返回false。因此,如果两个参数都为null,则返回true,如果只有一个参数为null,则返回false。否则,将使用第一个参数的equals方法来确定相等性。

5
这可以处理空值,因此使它更简单。谢谢! - Darren Parker
public static boolean equals(Object a, Object b) { return (a == b) || (a != null && a.equals(b));//这里处理了null } - SRK

24

在比较两个整数时,我们应该始终使用equals()方法。这是推荐的做法。

如果我们使用==比较两个整数,那么只有在特定范围的整数值(从-128到127的整数)内才能正常工作,这是由于JVM的内部优化造成的。

请参阅示例:

案例1:

Integer a = 100;
Integer b = 100;

if (a == b) {
  System.out.println("a and b are equal");
} else {
  System.out.println("a and b are not equal");
}
在上述情况下,JVM使用缓存池中a和b的值并返回整数对象的相同实例(因此是相同的内存地址),我们得到两者相等。这是JVM针对某些范围值进行的优化。 案例2:在这种情况下,a和b不相等,因为它们不在-128到127的范围内。
Integer a = 220;
Integer b = 220;
   
if (a == b) {
  System.out.println("a and b are equal");
} else {
  System.out.println("a and b are not equal");
}

正确的方式:

Integer a = 200;             
Integer b = 200;  
System.out.println("a == b? " + a.equals(b)); // true

谢谢 :) 我的代码运行了2年直到现在...数据库索引增长,有些大于128,导致数据库更新停止工作。这就是情况... - Hiro
由于JVM的内部优化,这似乎暗示着在JVM的内部有一些神奇的事情正在发生。实际上,对于这个Integer值范围的行为是由Java语言规范强制规定的;请参考Jon Skeet的回答中的JLS引用。 - undefined

11

== 检查引用相等性,但是当编写如下代码时:

Integer a = 1;
Integer b = 1;

Java足够聪明,可以重用相同的不可变对象ab,因此这是正确的:a == b。好奇心驱使,我写了一个小例子来展示Java在何时停止采用这种优化方式:

public class BoxingLol {
    public static void main(String[] args) {
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            Integer a = i;
            Integer b = i;
            if (a != b) {
                System.out.println("Done: " + i);
                System.exit(0);
            }
        }
        System.out.println("Done, all values equal");
    }
}

当我编译并运行此代码(在我的电脑上)时,得到如下结果:

Done: 128

3
简短概括:手势挥动-1;https://dev59.com/j2Up5IYBdhLWcg3wx5tu;https://dev59.com/h2Ei5IYBdhLWcg3w2PSB;https://dev59.com/dnA75IYBdhLWcg3wv7wj等详细解释了你提到的问题;阅读文档(或库源代码)比创建伪测试更好,因为结果可能具有高局部性——你不仅完全忘记了缓存的下限(默认为-128),而且你还有一个偏移量(最大值是127,而不是128)。 - user719662
但你完全无法保证在任何机器上收到相同的结果 - 因为你可以很容易地增加缓存大小,所以效果可能会有所不同。此外,OP的问题是如何正确比较两个整数 - 你根本没有回答 - user719662
我尊重你在这里的意见和看法。我认为我们在计算机科学方面只是有根本不同的方法。 - Cory Kendall
2
这与观点看法无关 - 它关乎你真正错过的事实。进行一个没有任何硬性支持数据(文档、源代码等)且没有回答OP问题的伪测试,不值得被称为良好的Q&A或CS。至于“不同的方法” - CS是一门科学;你所做的不是科学;它是误导性的琐事(如果正确陈述,它将是有趣的评论)- 如果你希望它成为科学,请纠正你的答案中的基本缺陷以明智的方式揭穿它们,因为这就是同行评审的工作方式。 - user719662
1
回答你的更大问题,我学习编程和教授编程的方法不是通过阅读文档,而是通过观察我们周围的世界(工具、语言、框架等)并观察结果。然而,你提出了一个很好的观点,这实际上更像是一条评论。鉴于SO没有让我发布包括代码在内的更长的探索性评论的方式,我现在将其留作答案。如果你投票关闭,我不会感到冒犯,并且会让投票/系统处理它。 - Cory Kendall
显示剩余3条评论

10

简而言之,我的意见是在检查值相等时使用一元+触发其中一个操作数的拆箱,否则只需使用数学运算符。下面是理由:

已经提到过,Integer==比较是身份比较,这通常不是程序员想要的,目标是进行值比较;尽管如此,我还是对如何最有效地进行比较进行了一些小小的科学研究,无论是在代码紧凑性、正确性还是速度方面。

我使用了通常的一堆方法:

public boolean method1() {
    Integer i1 = 7, i2 = 5;
    return i1.equals( i2 );
}

public boolean method2() {
    Integer i1 = 7, i2 = 5;
    return i1.intValue() == i2.intValue();
}

public boolean method3() {
    Integer i1 = 7, i2 = 5;
    return i1.intValue() == i2;
}

public boolean method4() {
    Integer i1 = 7, i2 = 5;
    return i1 == +i2;
}

public boolean method5() { // obviously not what we want..
    Integer i1 = 7, i2 = 5;
    return i1 == i2;
}

编译和反编译后得到了这段代码:

public boolean method1() {
    Integer var1 = Integer.valueOf( 7 );
    Integer var2 = Integer.valueOf( 5 );

    return var1.equals( var2 );
}

public boolean method2() {
    Integer var1 = Integer.valueOf( 7 );
    Integer var2 = Integer.valueOf( 5 );

    if ( var2.intValue() == var1.intValue() ) {
        return true;
    } else {
        return false;
    }
}

public boolean method3() {
    Integer var1 = Integer.valueOf( 7 );
    Integer var2 = Integer.valueOf( 5 );

    if ( var2.intValue() == var1.intValue() ) {
        return true;
    } else {
        return false;
    }
}

public boolean method4() {
    Integer var1 = Integer.valueOf( 7 );
    Integer var2 = Integer.valueOf( 5 );

    if ( var2.intValue() == var1.intValue() ) {
        return true;
    } else {
        return false;
    }
}

public boolean method5() {
    Integer var1 = Integer.valueOf( 7 );
    Integer var2 = Integer.valueOf( 5 );

    if ( var2 == var1 ) {
        return true;
    } else {
        return false;
    }
}

正如您可以轻松地看到的那样,方法1调用Integer.equals()(显然),方法2-4会产生完全相同的代码,通过.intValue()解包值,然后直接进行比较,而方法5只触发一个身份比较,是比较值的不正确的方式。

由于(如JS已经提到的)equals()会产生开销(它必须执行instanceof和未检查的转换),当在紧密循环中使用时,方法2-4将以完全相同的速度工作,明显比方法1更好,因为HotSpot不太可能优化掉转换和instanceof

其他比较运算符(例如</>)也是类似的 - 它们会触发拆箱,而使用compareTo()则不会 - 但这次,操作非常可优化,因为intValue()只是一个getter方法(被优化掉的主要候选人)。

在我看来,很少使用的版本4是最简洁的方式 - 每个经验丰富的C/Java开发人员都知道一元加号在大多数情况下等同于强制转换为int/.intValue() - 尽管对于一些人来说可能有点WTF(主要是那些从未使用过一元加号的人),但它可以明确和简洁地显示意图 - 它表明我们想要一个操作数的int值,同时强制另一个值解包。这也无疑与用于原始int值的常规i1 == i2比较最相似。
我的投票是支持i1 == +i2i1 > i2样式的Integer对象,出于性能和一致性的原因。它还使代码可移植到原语而不需要更改除类型声明之外的任何内容。对我来说,使用命名方法似乎像引入语义噪声,类似于备受批评的bigInt.add(10).multiply(-3)样式。

你能解释一下方法4中的+符号是什么意思吗?我试着在谷歌上搜索,但只找到了该符号的普通用法(加法、连接)。 - Alex Li
1
@AlexLi,它的意思就是我所写的 - unary +(一元加号),请参见例如 https://dev59.com/O3E85IYBdhLWcg3wwWat - user719662
关于equals调用的开销,JIT编译器应该将该调用的代码内联,然后通过窥孔优化生成与一元+技巧相等的本机代码。 - undefined

8

调用

if (a == b)

会大部分时间工作,但不能保证始终可用,因此不建议使用它。

比较两个 Integer 类型的最适当方法,假设它们被命名为 'a' 和 'b',是调用:

if(a != null && a.equals(b)) {
  System.out.println("They are equal");
}

您也可以使用这种稍微快一些的方法。
   if(a != null && b != null && (a.intValue() == b.intValue())) {
      System.out.println("They are equal");
    } 

在我的电脑上,使用第一种方法进行99亿次操作需要47秒,而使用第二种方法只需要46秒。您需要比较数十亿个值才能看到任何差异。

请注意,因为'a'是一个对象,所以它可能为空。使用这种方式进行比较不会导致空指针异常。

对于大于或小于的比较,请使用

if (a != null && b!=null) {
    int compareValue = a.compareTo(b);
    if (compareValue > 0) {
        System.out.println("a is greater than b");
    } else if (compareValue < 0) {
        System.out.println("b is greater than a");
    } else {
            System.out.println("a and b are equal");
    }
} else {
    System.out.println("a or b is null, cannot compare");
}

1
如果(a==b)仅适用于小值,并且大多数情况下不起作用。 - Tony
它可以工作到127,因为这是Java默认的整数缓存,它确保所有小于等于127的数字具有相同的引用值。如果您愿意,可以将缓存设置得比127更高,但为了安全起见,请不要使用==。 - otterslide
是的,“大部分时间都能工作”这个说法太宽泛了……你应该重新措辞。 - Stefan Reich

1
在我的情况下,我需要比较两个可能为nullInteger是否相等。我查找了类似的主题,但没有找到优雅的解决方案。我想出了一个简单的实用函数:
public static boolean integersEqual(Integer i1, Integer i2) {
    if (i1 == null && i2 == null) {
        return true;
    }
    if (i1 == null && i2 != null) {
        return false;
    }
    if (i1 != null && i2 == null) {
        return false;
    }
    return i1.intValue() == i2.intValue();
}

// Considering null is less than not-null
public static int integersCompare(Integer i1, Integer i2) {
    if (i1 == null && i2 == null) {
        return 0;
    }
    if (i1 == null && i2 != null) {
        return -1;
    }
    return i1.compareTo(i2);
}

-1

因为比较方法必须基于类型int(x==y)或类Integer(x.equals(y))使用正确的运算符:

public class Example {

    public static void main(String[] args) {
        int[] arr = {-32735, -32735, -32700, -32645, -32645, -32560, -32560};

        for(int j=1; j<arr.length-1; j++)
            if((arr[j-1] != arr[j]) && (arr[j] != arr[j+1]))
                System.out.println("int>" + arr[j]);

        Integer[] I_arr = {-32735, -32735, -32700, -32645, -32645, -32560, -32560};

        for(int j=1; j<I_arr.length-1; j++)
            if((!I_arr[j-1].equals(I_arr[j])) && (!I_arr[j].equals(I_arr[j+1])))
                System.out.println("Interger>" + I_arr[j]);
    }
}

-2

这个方法通过空值检查比较两个整数。请参见测试。

public static boolean compare(Integer int1, Integer int2) {
    if(int1!=null) {
        return int1.equals(int2);
    } else {
        return int2==null;
    }
    //inline version:
       //return (int1!=null) ? int1.equals(int2) : int2==null;
}

//results:
System.out.println(compare(1,1));           //true
System.out.println(compare(0,1));           //false
System.out.println(compare(1,0));           //false
System.out.println(compare(null,0));        //false
System.out.println(compare(0,null));        //false
System.out.println(compare(null,null));        //true

6
我认为最好使用Objects.equals(x,y)方法,而不是自己编写代码。 - ryvantage

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