ChatGPT解决这个技术问题 Extra ChatGPT

How can I properly compare two Integers in Java?

I know that if you compare a boxed primitive Integer with a constant such as:

Integer a = 4;
if (a < 5)

a will automatically be unboxed and the comparison will work.

However, what happens when you are comparing two boxed Integers and want to compare either equality or less than/greater than?

Integer a = 4;
Integer b = 5;

if (a == b)

Will the above code result in checking to see if they are the same object, or will it auto-unbox in that case?

What about:

Integer a = 4;
Integer b = 5;

if (a < b)

?

Well, what happened when you tried? What did you observe?
@Bart Kiers: An explicit experiment could only disprove, not prove that unboxing occurs. If using == instead of equals yields the correct result, that may be because the boxed numbers are being interned or otherwise reused (as a compiler optimization, presumably). The reason to ask this question is to find out what's happening internally, not what appears to be happening. (At least, that's why I'm here.)
Rant. By far the stupidest thing about Java is the inability to override operators, such as == and < to do something sensible, for example, with String and Integer types. Therefore you have to use a.equals(b) or b.equals(a) instead. And if you want to handle null (as you ought!) you have to use Objects.equals(a,b).
I tried Integer a = 4 Integer b = 4; a==b retruns false. I had to use if (x.intValue() == y.intValue())

J
Jon Skeet

No, == between Integer, Long etc will check for reference equality - i.e.

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

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

this will check whether x and y refer to the same object rather than equal objects.

So

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

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

is guaranteed to print false. Interning of "small" autoboxed values can lead to tricky results:

Integer x = 10;
Integer y = 10;

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

This will print true, due to the rules of boxing (JLS section 5.1.7). It's still reference equality being used, but the references genuinely are equal.

If the value p being boxed is an integer literal of type int between -128 and 127 inclusive (§3.10.1), or the boolean literal true or false (§3.10.3), or a character literal between '\u0000' and '\u007f' inclusive (§3.10.4), then let a and b be the results of any two boxing conversions of p. It is always the case that a == b.

Personally I'd use:

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

or

if (x.equals(y))

As you say, for any comparison between a wrapper type (Integer, Long etc) and a numeric type (int, long etc) the wrapper type value is unboxed and the test is applied to the primitive values involved.

This occurs as part of binary numeric promotion (JLS section 5.6.2). Look at each individual operator's documentation to see whether it's applied. For example, from the docs for == and != (JLS 15.21.1):

If the operands of an equality operator are both of numeric type, or one is of numeric type and the other is convertible (§5.1.8) to numeric type, binary numeric promotion is performed on the operands (§5.6.2).

and for <, <=, > and >= (JLS 15.20.1)

The type of each of the operands of a numerical comparison operator must be a type that is convertible (§5.1.8) to a primitive numeric type, or a compile-time error occurs. Binary numeric promotion is performed on the operands (§5.6.2). If the promoted type of the operands is int or long, then signed integer comparison is performed; if this promoted type is float or double, then floating-point comparison is performed.

Note how none of this is considered as part of the situation where neither type is a numeric type.


Is there any reason why one would want to write x.compareTo(y) < 0 instead of x < y?
@MaxNanasy: Not that I can immediately think of.
As of Java 1.6.27+ there an overload on equals in the Integer class, so it should be as efficient as calling .intValue(). It compares the values as primitive int.
As @otterslide said, this is not necessary in Java 8 anymore. The comparison of Integer with Integer is by value by default.
@Axel: The addition of an overload wouldn't change the behaviour of the == operator though, would it? I'm not in a position to test right now, but I'd be very surprised if that had changed.
A
Adam Lewis

== will still test object equality. It is easy to be fooled, however:

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

Your examples with inequalities will work since they are not defined on Objects. However, with the == comparison, object equality will still be checked. In this case, when you initialize the objects from a boxed primitive, the same object is used (for both a and b). This is an okay optimization since the primitive box classes are immutable.


I figured it was object equality being tested. I had some weird results. Should I replace it with .equals()? Also, do you feel I should leave the inequalities as they are or do it another way as well?
There are some non-obvious edge cases with autoboxing. I have my IDE (Eclipse) set to color anything being un-boxed in red, this has saved me from bugs on a few occasions. If you are comparing two Integers, use .equals, if you want to make your inequalities clear, write the cast in explicitly: if ((int)c < (int)d) ... ; You can also do: c.compareTo(d) < 0 // === c < d
And if you change the number literals to 200, both tests will print false.
... on most JVM implementations, that is. According to the language spec the result could vary between implementations.
I think it's clearer to call this "reference equality" - that way it's obvious what you mean. I'd normally understand "object equality" to mean "the result of equals being called".
J
Justinas Jakavonis

Since Java 1.7 you can use Objects.equals:

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

Returns true if the arguments are equal to each other and false otherwise. Consequently, if both arguments are null, true is returned and if exactly one argument is null, false is returned. Otherwise, equality is determined by using the equals method of the first argument.


This handles nulls so makes it simple. Thanks!
public static boolean equals(Object a, Object b) { return (a == b) || (a != null && a.equals(b));//This handle null }
P
Peter Mortensen

We should always go for the equals() method for comparison of two integers. It's the recommended practice.

If we compare two integers using == that would work for certain range of integer values (Integer from -128 to 127) due to the JVM's internal optimisation.

Please see examples:

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

In above case JVM uses value of a and b from cached pool and return the same object instance(therefore memory address) of integer object and we get both are equal.Its an optimisation JVM does for certain range values.

Case 2: In this case, a and b are not equal because it does not come with the range from -128 to 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");
}

Proper way:

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

佚名

tl;dr my opinion is to use a unary + to trigger the unboxing on one of the operands when checking for value equality, and simply use the maths operators otherwise. Rationale follows:

It has been mentioned already that == comparison for Integer is identity comparison, which is usually not what a programmer want, and that the aim is to do value comparison; still, I've done a little science about how to do that comparison most efficiently, both in term of code compactness, correctness and speed.

I used the usual bunch of methods:

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

and got this code after compilation and decompilation:

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

As you can easily see, method 1 calls Integer.equals() (obviously), methods 2-4 result in exactly the same code, unwrapping the values by means of .intValue() and then comparing them directly, and method 5 just triggers an identity comparison, being the incorrect way to compare values.

Since (as already mentioned by e.g. JS) equals() incurs an overhead (it has to do instanceof and an unchecked cast), methods 2-4 will work with exactly the same speed, noticingly better than method 1 when used in tight loops, since HotSpot is not likely to optimize out the casts & instanceof.

It's quite similar with other comparison operators (e.g. </>) - they will trigger unboxing, while using compareTo() won't - but this time, the operation is highly optimizable by HS since intValue() is just a getter method (prime candidate to being optimized out).

In my opinion, the seldom used version 4 is the most concise way - every seasoned C/Java developer knows that unary plus is in most cases equal to cast to int/.intValue() - while it may be a little WTF moment for some (mostly those who didn't use unary plus in their lifetime), it arguably shows the intent most clearly and most tersely - it shows that we want an int value of one of the operands, forcing the other value to unbox as well. It is also unarguably most similar to the regular i1 == i2 comparison used for primitive int values.

My vote goes for i1 == +i2 & i1 > i2 style for Integer objects, both for performance & consistency reasons. It also makes the code portable to primitives without changing anything other than the type declaration. Using named methods seems like introducing semantic noise to me, similar to the much-criticized bigInt.add(10).multiply(-3) style.


Can you explain what the + means in method 4? I tried to google it but I only got the normal usages of that symbol (addition, concatenation).
@AlexLi it means exactly what I wrote - unary + (unary plus), see e.g. stackoverflow.com/questions/2624410/…
C
Cory Kendall

== checks for reference equality, however when writing code like:

Integer a = 1;
Integer b = 1;

Java is smart enough to reuse the same immutable for a and b, so this is true: a == b. Curious, I wrote a small example to show where java stops optimizing in this way:

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

When I compile and run this (on my machine), I get:

Done: 128

tl;dr -1 for handwaving ; stackoverflow.com/questions/15052216/… stackoverflow.com/questions/20897020/… stackoverflow.com/questions/3131136/integers-caching-in-java etc explain in-detail the matter you mentioned; it's better to read the docs (or lib source) than to create pseudo-tests with the risk of high locality of the results - not only have you completely forgotten about the lower bound of the cache (i.e. -128 by default), not only you have off-by-one (the max is 127, not 128),
but you have completely no guarantee to receive the same result on any machine - since you can easily increase the cache size yourself, YMMV. Also, OP's question was how to properly compare two Integers - you haven't answered it at all.
I respect your opinion and perception here. I think we just have fundamentally different approaches to CS.
it's not about opinion nor perception - it's about the facts, which you sincerelly missed. Doing a pseudo-test proving nothing, without any hard backing data (docs, source etc.) and without answering OP's questions doesn't deserve to be called neither good Q&A nor CS. As to "different approach" - CS is, by definition, a science; what you did science is not; it's a misleading trivia (or it would be an intriguing comment, if stated properly) - if you wish it to be science, correct the fundamental flaws in your answer or debunk them sensibly, as that's how peer review works.
Sure then, I'll try to address the flaws. I didn't forget about the lower bound, I didn't feel it was interesting and chose not to include it. I don't believe I have an off by one error, I stated the way that java (which I clarified on my machine, in my situation) stopped optimizing this was, which is at 128. If I had stated the max value it did this for, than you're right the answer would have been 127.
o
otterslide

Calling

if (a == b)

Will work most of the time, but it's not guaranteed to always work, so do not use it.

The most proper way to compare two Integer classes for equality, assuming they are named 'a' and 'b' is to call:

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

You can also use this way which is slightly faster.

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

On my machine 99 billion operations took 47 seconds using the first method, and 46 seconds using the second method. You would need to be comparing billions of values to see any difference.

Note that 'a' may be null since it's an Object. Comparing in this way will not cause a null pointer exception.

For comparing greater and less than, use

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) works only for small values and will not work most of the times.
It works up to 127 as that is Java's default Integer cache, which makes sure all numbers up to 127 have the same reference value. You can set the cache to go higher than 127 if you'd like, but just don't use == to be safe.
Yeah "Will work most of the time" is extremely generous... you should really reword that
P
Peter Mortensen

In my case I had to compare two Integers for equality where both of them could be null. I searched similar topics, but I didn't find anything elegant for this. I came up with simple utility function:

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

Because a comparison method has to be done based on type int (x==y) or class Integer (x.equals(y)) with the right operator:

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

This method compares two Integer's with a null check. See the tests.

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

For this, I think it'd just be better to use Objects.equals(x,y) method instead of rolling your own.