6

In a program I was working on, I ran into a data storage issue, specifically related to ArrayLists. This is not the actual code I was testing, but it provides an example of what I mean.

public class test
{
  public static void test()
  {
    ArrayList<Integer> bob = new ArrayList<Integer>();
    bob.add(129);
    bob.add(129);
    System.out.println(bob.get(0) == 129 );
    System.out.println(bob.get(1) == 129 );
    System.out.println(bob.get(0)  == bob.get(1) );
  }
}

If you run it, you get, true, true, and false. The code recognizes that both are equal to 129 but for some reason returns false when it attempts to see if they are equal to each other. However, if you change the value to 127, it returns true, true, and true. Testing this multiple times for different values and you will see that the minimum value to receive true, true, and true is -128 and the maximum is 127. This is the interval for byte, which leads me to suspect that the == operation uses byte in this case.

What is interesting is that if you modify the code so that it reads

public class test
{
  public static void test()
  {
    ArrayList<Integer> bob = new ArrayList<Integer>();
    bob.add(129);
    bob.add(129);
    int a = bob.get(0);
    int b = bob.get(1);

    System.out.println(a == 129 );
    System.out.println(b == 129 );
    System.out.println(a == b );
  }
}

it works just as intended. true, true, and true are outputted. Why does saving the values as int before the comparison change the outcome? Is it because if they are not saved, the comparison will use byte by default for the == comparison?

Sanum
  • 63
  • 4
  • Integer is a class and you're adding an Integer object into the ArrayList. Also when you .get() it should return two instances of Integer object. The comparison result should be comparing the object instance. When you use a native type int to hold the values and use the comparison operator, it compares the value. – Ken Cheung May 29 '14 at 05:04

6 Answers6

3

The answer lies in the caching mechanism of the primitive wrapper classes that Java employs.
In the case of an Integer, there's caching for the values between -128 to 127 (i.e. the value range of a byte).

This means that if you box any value between -128 to 127, you get a ready made instance from the cache. This is why the == operator works for those, as it compares the references rather than the values.
On the other hand, if you're using any other value, you'll get a fresh new instance per boxing, which means that the == operator will fail.

Here's the piece of code from the Integer class that's responsible for this:

private static class IntegerCache {
    private IntegerCache(){}

    static final Integer cache[] = new Integer[-(-128) + 127 + 1];

    static {
        for(int i = 0; i < cache.length; i++)
            cache[i] = new Integer(i - 128);
    }
}
ethanfar
  • 3,733
  • 24
  • 43
  • I'm a beginner in java (just finished AP Computer Science A) and this answer made the most sense to me. All of the other answers I've read were great and would probably be very easy to comprehend for a programmer more advanced than me. However, since I'm a beginner, and don't know too much about autoboxing or caching, this answer was the most clear for me. Thanks to everyone who responded; I really appreciate all of your help. – Sanum May 29 '14 at 05:43
2

Right, I just realized that I can explain this. Don't know what I was thinking earlier.

JLS, section 5.1.7:

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.

As 129 falls outside this range, you'll end up with two distinct Integer objects in indices 1 and 2, unless you configure the range yourself using JVM flags.

For the last comparison in the first bit of code: As ArrayList#get() returns an object of the type the ArrayList is parameterized with, that last comparison is comparing two Integer objects, and as the two objects are distinct, the result will be false. The first two comparisons result in the Integer objects being unboxed, because you're comparing an Integer wrapper to an int literal, so the results are as you expect.

The second bit of code works as you expect because you're comparing int literals, and those comparisons are more intuitive.

awksp
  • 11,764
  • 4
  • 37
  • 44
1

That's because the third test compares two objects because the (Integer object) return of the get() calls are not unboxed. The values for which the result is true are using cached singletons and therefore the same object, but outside of that range new and distinct objects are put into the list by auto-boxing.

Take note that this behavior could vary from JVM to JVM and version to version; on some JVMs it could even be dynamic based on some heuristic - for example, the system could conceivably look at available memory and cache 16 bit values instead of 8.

Lawrence Dol
  • 63,018
  • 25
  • 139
  • 189
1

Autoboxing and unboxing are at work, this works -

bob.add(129);       // Autoboxed to Integer
int a = bob.get(0); // primitive int a
int b = bob.get(1); // primitive int b

System.out.println(a == 129 ); // primitive to primitive
System.out.println(b == 129 ); // primitive to primitive
System.out.println(a == b ); // primitive to primitive

You could also use Integer.intValue(),

Integer a = bob.get(0);
Integer b = bob.get(1);
// Here you could omit one, but not both, calls to intValue()
System.out.println(a.intValue() == b.intValue()); // primitive to primitive
Elliott Frisch
  • 198,278
  • 20
  • 158
  • 249
1

This is because first two comparisons are on int values because bob.get() is getting casted to int before comparison. in the third, comparison is on Objects and that is the reason you are getting false for values outside -128 to 127 because in this range values are cached.

Hope this helps.

Sanjeev
  • 9,876
  • 2
  • 22
  • 33
1

Collections have 2 get methods that accept int and Integer with Integer collections, autoboxing is doing some internal magic to use the wrong method (Effective Java)

Use explicit boxing or unbox as necessary.