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


215

我知道,如果将装箱的原始整数与常量进行比较,例如:

Integer a = 4;
if (a < 5)

a 将自动取消装箱,比较将起作用。

但是,当您比较两个装箱的Integers并且想要比较等于或小于/大于/大于时,会发生什么?

Integer a = 4;
Integer b = 5;

if (a == b)

上面的代码是否会导致检查它们是否是同一对象,或者在这种情况下会自动拆箱?

关于什么:

Integer a = 4;
Integer b = 5;

if (a < b)


16
好吧,当您尝试时发生了什么?你观察到了什么?
巴特·基尔斯

31
@Bart Kiers:一个明确的实验只能反驳,不能证明拆箱的发生。如果使用==而不是equals产生正确的结果,那可能是因为装箱的数字正在被求数或以其他方式重用(可能是作为编译器的优化)。提出此问题的原因是要找出内部正在发生的事情,而不是看起来正在发生的事情。(至少,这就是为什么我在这里。)
Jim Pivarski 2013年

您的帐户怎么了?
96 94

Answers:


301

不,在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。对“小的”自动装箱的值进行internet可能会导致棘手的结果:

Integer x = 10;
Integer y = 10;

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

true根据装箱规则(JLS第5.1.7节),将进行打印。它仍然使用引用相等,但是引用实际上是相等的。

如果要装箱的值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第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,则执行浮点比较。

请注意,这两种类型都不属于数字类型。


2
有什么理由为什么要写x.compareTo(y) < 0而不是写x < y
Max Nanasy

1
@MaxNanasy:不是我马上想到的。
乔恩·斯基特

2
从Java 1.6.27+开始,Integer类中的equals有一个重载,因此它应与调用.intValue()一样有效。它将值比较为原始int。
otterslide

正如@otterslide所说,在Java 8中不再需要此功能。默认情况下,按值比较Integer与Integer。
Axel Prieto

1
@Axel:不过,重载的添加不会改变==运算符的行为,对吗?我现在无法进行测试,但是如果情况有所改变,我会感到非常惊讶。
乔恩·斯基特

44

==仍将测试对象是否相等。很容易上当,但是:

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)。这是可以的优化,因为基本框类是不可变的。


我认为这是正在测试的对象相等性。我有一些奇怪的结果。我应该用.equals()代替吗?另外,您是否认为我应该保留不平等现象,还是另辟qual径?

自动装箱有一些不太明显的边缘情况。我将我的IDE(Eclipse)设置为将任何未装箱的东西都涂成红色,这使我在某些情况下免于错误。如果要比较两个Integer,请使用.equals。如果要清除不等式,请显式写入强制转换:if(((int)c <(int)d)...; 您也可以这样做:c.compareTo(d)<0 // === c <d
亚当·刘易斯2009年

12
并且,如果将数字文字更改为200,则将打印两个测试false
Daniel Earwicker,2009年

2
...在大多数JVM实现中。根据语言规范,结果可能在不同的实现之间有所不同。
Daniel Earwicker,2009年

4
我认为将其称为“引用相等”更为清晰-这样就可以清楚地理解您的意思。我通常会理解“对象相等”是指“ equals被调用的结果”。
Jon Skeet

28

从Java 1.7开始,您可以使用Objects.equals

java.util.Objects.equals(oneInteger, anotherInteger);

如果参数彼此相等,则返回true;否则返回false。因此,如果两个参数都为null,则返回true;如果正好一个参数为null,则返回false。否则,将使用第一个参数的equals方法确定相等性。


它处理空值,因此很简单。谢谢!
达伦·帕克

10

== 检查引用是否相等,但是在编写类似以下代码时:

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

1
tl; dr -1用于手挥动;stackoverflow.com/questions/15052216/... stackoverflow.com/questions/20897020/... stackoverflow.com/questions/3131136/integers-caching-in-java等,在细节你所提到的事情解释; 最好是阅读文档(或lib源代码),而不是创建伪测试,否则可能会导致结果局部性很高-不仅让您完全忘记了缓存的下限(默认情况下为-128),不仅您的

但您完全不能保证在任何计算机上都能收到相同的结果-因为您可以自己轻松地增加缓存大小,即YMMV。另外,OP的问题是如何正确比较两个Integer-根本没有回答

我在这里尊重您的意见和看法。我认为我们在CS方面有根本不同的方法。
科里·肯德尔

1
这与意见感知无关- 与您真诚地遗漏的事实有关。没有任何可靠的数据(文档,源等)并且没有回答OP的问题,进行伪测试不会证明任何事情,这既不被称为良好的问答,也不被称为CS。关于“不同的方法”,从定义上讲,CS是一门科学;你做科学的不是 ; 这是一个误导性的琐事(或者,如果正确陈述,那将是一个有趣的评论)-如果您希望这是科学,请纠正您的答案中的根本性缺陷明智地揭穿它们,因为这就是有效的方法。

当然可以,我将尝试解决这些缺陷。我没有忘记下限,我不觉得这很有趣,因此选择不包括它。我不相信我有一个错误,我说过Java(在我的情况下在我的机器上澄清过)停止优化的方式是在128。如果我说了它的最大值此为,比你说的没错,答案将是127
科里·肯德尔

8

tl; dr我的意见是+在检查值相等时使用一元触发操作数之一上的拆箱操作,否则简单地使用数学运算符。基本原理如下:

已经提到过,==用于比较的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将以完全相同的速度工作,值得注意的是,在紧密循环中使用时,方法2-4比方法1更好,因为HotSpot并非如此可能优化演员和演员instanceof

与其他比较运算符(例如</ >)非常相似-它们将触发拆箱,而compareTo()不会使用--但这一次,HS高度优化了该操作,因为intValue()这只是一种吸气方法(被优化的主要候选者)。

在我看来,将很少使用的版本4是最简洁的方式-每一个经验丰富的C / Java开发人员都知道,一元加在大多数情况下等于投地int/ .intValue()-虽然它可能是一个小WTF的某一时刻(主要是那些谁没(在其生命周期中不使用一元加号),可以说它最清楚,最简洁地显示了其意图-它表明我们想要一个int操作数的值,同时也迫使另一个值取消装箱。毫无疑问,它也i1 == i2与用于原始int值的常规比较最为相似。

我出于性能和一致性的考虑,投票赞成i1 == +i2i1 > i2拒绝Integer对象的样式。它还使代码可移植到基元,而不更改类型声明以外的任何内容。使用命名方法似乎给我引入了语义干扰,类似于广受批评的bigInt.add(10).multiply(-3)样式。


您能否解释方法4中的+含义?我尝试用谷歌搜索,但只得到了该符号的正常用法(加法,串联)。
亚历克斯·李

1
@AlexLi表示我写的确切内容unary +((一元加号),请参见例如stackoverflow.com/questions/2624410/…–

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");
    } 

在我的机器上,使用第一种方法进行的990亿次操作耗时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
if (a==b)仅适用于较小的值,在大多数情况下无效。
托尼

它最多可使用127,因为这是Java的默认Integer高速缓存,可确保127以下的所有数字都具有相同的参考值。您可以根据需要将缓存设置为大于127,但是为了安全起见,请勿使用==。
otterslide

2

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

如果我们使用==比较两个整数,则由于JVM的内部优化,该整数将适用于一定范围的整数值(从-128到127的整数)。

请参见示例:

情况1:

整数a = 100; 整数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。

整数a = 220; 整数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

我希望这有帮助。


1

就我而言,我必须比较两个Integers的相等性,使两者都可以null。搜索了类似的主题,没有发现任何适合的话题。想到了一个简单的实用程序功能。

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

4
为此,我认为使用Objects.equals(x,y)方法而不是自己动手会更好。
ryvantage
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.