ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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)

?

那么,当你尝试时发生了什么?你观察到了什么?
@Bart Kiers:一个明确的实验只能反驳,不能证明拆箱的发生。如果使用 == 而不是 equals 产生正确的结果,那可能是因为装箱的数字正在被保留或以其他方式重用(大概是作为编译器优化)。问这个问题的原因是要找出内部正在发生的事情,而不是看起来正在发生的事情。 (至少,这就是我在这里的原因。)
咆哮。到目前为止,Java 最愚蠢的事情是无法覆盖运算符,例如 == 和 < 来做一些明智的事情,例如,使用 String 和 Integer 类型。因此,您必须改用 a.equals(b) 或 b.equals(a) 。如果你想处理 null(你应该这样做!)你必须使用 Objects.equals(a,b)。
我试过 Integer a = 4 Integer b = 4; a==b 返回错误。我不得不使用 if (x.intValue() == y.intValue())

J
Jon Skeet

不,== 在 Integer、Long 等之间将检查引用是否相等 - 即

Integer x = ...;
Integer y = ...;

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

这将检查 xy 是否引用 相同的对象 而不是 equal 对象。

所以

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 section 5.1.7),这将打印 true。它仍然使用引用相等,但引用真正相等。

如果被装箱的值 p 是 int 类型的整数文字,介于 -128 和 127 之间(第 3.10.1 节),或者布尔文字真或假(第 3.10.3 节),或者是介于 '\u0000' 和'\u007f' (第 3.10.4 节),则令 a 和 b 为 p 的任意两次装箱转换的结果。 a == b 总是如此。

我个人会使用:

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

或者

if (x.equals(y))

正如您所说,对于包装类型(IntegerLong 等)和数字类型(intlong 等)之间的任何比较,包装类型值是 unboxed 并且test 应用于所涉及的原始值。

这作为二进制数字提升 (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,则执行浮点比较。

请注意,这些都不被视为两种类型都不是数字类型的情况的一部分。


有什么理由要写 x.compareTo(y) < 0 而不是 x < y
@MaxNanasy:不是我能立即想到的。
从 Java 1.6.27+ 开始,Integer 类中的 equals 有一个重载,因此它应该与调用 .intValue() 一样高效。它将值作为原始 int 进行比较。
正如@otterslide 所说,这在 Java 8 中不再是必需的。 Integer 与 Integer 的比较默认是按值。
@Axel:添加重载不会改变 == 运算符的行为,不是吗?我现在无法进行测试,但如果情况发生了变化,我会感到非常惊讶。
A
Adam Lewis

== 仍将测试对象相等性。然而,很容易被愚弄:

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() 替换它吗?另外,你觉得我应该让不平等保持原样还是换一种方式?
自动装箱有一些不明显的边缘情况。我将我的 IDE (Eclipse) 设置为将任何未装箱的东西都涂成红色,这在几次情况下使我免于出现错误。如果要比较两个整数,请使用 .equals,如果要明确不等式,请显式写入强制转换: if ((int)c < (int)d) ... ;你也可以这样做: c.compareTo(d) < 0 // === c < d
如果您将数字文字更改为 200,则两个测试都将打印 false
...在大多数 JVM 实现上,就是这样。根据语言规范,结果可能因实现而异。
我认为将其称为“引用相等”更清楚-这样您的意思就很明显了。我通常将“对象相等”理解为“调用 equals 的结果”。
J
Justinas Jakavonis

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

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

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


这可以处理空值,因此很简单。谢谢!
公共静态布尔等于(对象a,对象b){返回(a == b)|| (a != null && a.equals(b));//这个句柄 null }
P
Peter Mortensen

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

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

请看示例:

情况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

佚名

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 将以完全相同的速度工作,在紧密使用时明显优于方法 1循环,因为 HotSpot 不太可能优化演员阵容和instanceof

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

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

我投票给i1 == +i2 & Integer 个对象的 i1 > i2 样式,用于性能和一致性的原因。它还使代码可移植到原语,而无需更改类型声明以外的任何内容。使用命名方法似乎给我带来了语义噪音,类似于备受批评的 bigInt.add(10).multiply(-3) 风格。


你能解释一下方法 4 中的 + 是什么意思吗?我试图用谷歌搜索它,但我只得到了该符号的正常用法(加法、连接)。
@AlexLi 这正是我写的 - unary +(一元加号),参见例如 stackoverflow.com/questions/2624410/…
C
Cory Kendall

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

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

tl;dr -1 用于挥手; stackoverflow.com/questions/15052216/… stackoverflow.com/questions/20897020/… stackoverflow.com/questions/3131136/integers-caching-in-java 等详细解释您提到的问题;最好阅读文档(或 lib 源代码)而不是创建具有高局部性风险的伪测试 - 不仅您完全忘记了缓存的下限(即默认为 -128),不仅你有一对一(最大值是 127,而不是 128),
但是您完全不能保证在任何机器上都能收到相同的结果——因为您可以自己轻松地增加缓存大小,YMMV。此外,OP 的问题是如何正确比较两个整数 - 你根本没有回答。
我在这里尊重你的意见和看法。我认为我们只是对 CS 有根本不同的方法。
这与观点或看法无关——它与事实有关,您真诚地错过了这些事实。在没有任何硬性支持数据(文档、源代码等)并且没有回答 OP 的问题的情况下,做一个没有任何证明的伪测试不应该被称为既不是好的问答也不是 CS。至于“不同的方法”——根据定义,CS 是一门科学;你所做的科学不是;这是一个误导性的琐事(或者如果表述得当,这将是一个有趣的评论) - 如果您希望它是科学的,请纠正您的答案中的基本缺陷或明智地揭穿它们,因为这就是同行评审的工作方式。
当然,我会尝试解决这些缺陷。我没有忘记下界,我觉得它不有趣,所以选择不包括它。我不相信我有一个错误,我说java(我在我的机器上澄清,在我的情况下)停止优化这个的方式是128。如果我说它做了最大值这比你说的对,答案是 127。
o
otterslide

打电话

if (a == b)

大部分时间都会工作,但不能保证总是工作,所以不要使用它。

假设它们被命名为“a”和“b”,比较两个 Integer 类是否相等的最正确方法是调用:

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

if (a==b) 仅适用于较小的值,并且在大多数情况下都不起作用。
它最多可以工作 127,因为这是 Java 的默认整数缓存,它确保 127 之前的所有数字都具有相同的参考值。如果您愿意,可以将缓存设置为高于 127,但为了安全起见,不要使用 ==。
是的,“大部分时间都可以工作”非常慷慨……你真的应该改写
P
Peter Mortensen

在我的情况下,我必须比较两个 Integer 的相等性,它们都可能是 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);
}

P
Peter Mortensen

因为必须使用正确的运算符基于类型 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]);
    }
}

P
Peter Mortensen

此方法将两个整数与空检查进行比较。查看测试。

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

为此,我认为最好使用 Objects.equals(x,y) 方法而不是自己滚动。