Friday, October 2, 2009

Integers and Equality Operator

On a lucky day, you could get away by comparing Integers using the equality operator:

1:  public class IntTest {  
2: public static void main(String[] args) {
3: Integer a = 100;
4: Integer b = 100;
5: System.out.println(a == b);
6: }
7: }


Running IntTest.class results to:

 true  


But you may not find yourself lucky if you have a code similar to the one found below:

1:  public class IntTest2 {  
2: public static void main(String[] args) {
3: Integer a = 200;
4: Integer b = 200;
5: System.out.println(a == b);
6: }
7: }


Running IntTest2.class results to:

 false  



Let's write another equality test to check our sanity:

1:  public class IntBox {  
2: public static void main(String[] args) {
3: Integer a1 = 127;
4: Integer a2 = 127;
5: Integer b1 = -128;
6: Integer b2 = -128;
7: Integer c1 = 128;
8: Integer c2 = 128;
9: Integer d1 = -129;
10: Integer d2 = -129;
11: System.out.println("a1 == a1 " + (a1 == a2));
12: System.out.println("b1 == b2 " + (b1 == b2));
13: System.out.println("c1 == c2 " + (c1 == c2));
14: System.out.println("d1 == d2 " + (d1 == d2));
15: }
16: }


Running the code gives us:

 a1 == a1 true  
b1 == b2 true
c1 == c2 false
d1 == d2 false


Yes, comparing 127 and -128 results to true but not 128 and -129.

Let's disassemble IntBox.class to investigate further by running "javap -c IntBox":

1:  Compiled from "IntBox.java"  
2: public class IntBox extends java.lang.Object{
3: public IntBox();
4: Code:
5: 0: aload_0
6: 1: invokespecial #1; //Method java/lang/Object."<init>":()V
7: 4: return
8: public static void main(java.lang.String[]);
9: Code:
10: 0: bipush 127
11: 2: invokestatic #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
12: 5: astore_1
13: 6: bipush 127
14: 8: invokestatic #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
15: 11: astore_2
16: 12: bipush -128
17: 14: invokestatic #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
18: 17: astore_3
19: 18: bipush -128
20: 20: invokestatic #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
21: 23: astore 4
22: 25: sipush 128
23: 28: invokestatic #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
24: 31: astore 5
25: 33: sipush 128
26: 36: invokestatic #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
27: 39: astore 6
28: 41: sipush -129
29: 44: invokestatic #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
30: 47: astore 7
31: 49: sipush -129
32: 52: invokestatic #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
33: 55: astore 8
34: 57: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream;
35: 60: new #4; //class java/lang/StringBuilder
36: 63: dup
37: 64: invokespecial #5; //Method java/lang/StringBuilder."<init>":()V
38: 67: ldc #6; //String a1 == a1
39: 69: invokevirtual #7; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
40: 72: aload_1
41: 73: aload_2
42: 74: if_acmpne 81
43: 77: iconst_1
44: 78: goto 82
45: 81: iconst_0
46: 82: invokevirtual #8; //Method java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder;
47: 85: invokevirtual #9; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
48: 88: invokevirtual #10; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
49: 91: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream;
50: 94: new #4; //class java/lang/StringBuilder
51: 97: dup
52: 98: invokespecial #5; //Method java/lang/StringBuilder."<init>":()V
53: 101: ldc #11; //String b1 == b2
54: 103: invokevirtual #7; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
55: 106: aload_3
56: 107: aload 4
57: 109: if_acmpne 116
58: 112: iconst_1
59: 113: goto 117
60: 116: iconst_0
61: 117: invokevirtual #8; //Method java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder;
62: 120: invokevirtual #9; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
63: 123: invokevirtual #10; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
64: 126: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream;
65: 129: new #4; //class java/lang/StringBuilder
66: 132: dup
67: 133: invokespecial #5; //Method java/lang/StringBuilder."<init>":()V
68: 136: ldc #12; //String c1 == c2
69: 138: invokevirtual #7; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
70: 141: aload 5
71: 143: aload 6
72: 145: if_acmpne 152
73: 148: iconst_1
74: 149: goto 153
75: 152: iconst_0
76: 153: invokevirtual #8; //Method java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder;
77: 156: invokevirtual #9; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
78: 159: invokevirtual #10; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
79: 162: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream;
80: 165: new #4; //class java/lang/StringBuilder
81: 168: dup
82: 169: invokespecial #5; //Method java/lang/StringBuilder."<init>":()V
83: 172: ldc #13; //String d1 == d2
84: 174: invokevirtual #7; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
85: 177: aload 7
86: 179: aload 8
87: 181: if_acmpne 188
88: 184: iconst_1
89: 185: goto 189
90: 188: iconst_0
91: 189: invokevirtual #8; //Method java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder;
92: 192: invokevirtual #9; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
93: 195: invokevirtual #10; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
94: 198: return
95: }



From line 10 to 33, the compiler converts our integer assignments to calling Integer.valueOf(int).

Looking at Integer.java source, the Integer class performs special cache operation whenever valueOf(int i) method is called:

(From JDK6):
1:  public static Integer valueOf(int i) {  
2: if(i >= -128 && i <= IntegerCache.high)
3: return IntegerCache.cache[i + 128];
4: else
5: return new Integer(i);
6: }


and for private static class IntegerCache found in Integer.java (neglect line 8 to 15 to keep things simple for now):

1:  private static class IntegerCache {  
2: static final int high;
3: static final Integer cache[];
4: static {
5: final int low = -128;
6: // high value may be configured by property
7: int h = 127;
8: if (integerCacheHighPropValue != null) {
9: // Use Long.decode here to avoid invoking methods that
10: // require Integer's autoboxing cache to be initialized
11: int i = Long.decode(integerCacheHighPropValue).intValue();
12: i = Math.max(i, 127);
13: // Maximum array size is Integer.MAX_VALUE
14: h = Math.min(i, Integer.MAX_VALUE - -low);
15: }
16: high = h;
17: cache = new Integer[(high - low) + 1];
18: int j = low;
19: for(int k = 0; k < cache.length; k++)
20: cache[k] = new Integer(j++);
21: }
22: private IntegerCache() {}
23: }


* Line 17 initialises the cache array to 256 in size, assuming 127 is the highest value
* Line 19 to 21 create the integer objects from -128 to 127 and stores them into "cache" variable

From the statements above, we see that Integers are cached starting from -128 to 127 and that suggests why a1/a2 and b1/b2 equality operator comparison works but not for c1/c2 and d1/d2.