How to properly compare two Integers in Java?

前端 未结 10 1363
情歌与酒
情歌与酒 2020-11-21 07:06

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

Integer a = 4;
if (a < 5)

a will automaticall

相关标签:
10条回答
  • 2020-11-21 07:26

    this method compares two Integer with null check, see 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
    
    0 讨论(0)
  • 2020-11-21 07:27

    == 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.

    0 讨论(0)
  • 2020-11-21 07:27

    In my case I had to compare two Integers for equality where both of them could be null. Searched similar topic, didn't found anything elegant for this. Came up with a simple utility functions.

    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);
    }
    
    0 讨论(0)
  • 2020-11-21 07:30

    == 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
    
    0 讨论(0)
  • 2020-11-21 07:34

    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.

    0 讨论(0)
  • 2020-11-21 07:36

    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.

    0 讨论(0)
提交回复
热议问题