Changes in the Java language have made the differences between
int
and
java.lang.Integer
less obvious but every Java developer should understand them.
Unless otherwise stated, Java 7 syntax and types are used.
Many of these issues apply to all the primitive types and their wrapper types.
This post has been rewritten in 2013. The original post was still generating comments (not the good kind) five years after it was written. This post is more detailed and provides better examples. Old comments have been deleted to avoid confusion - new criticism is welcome.
TL;DR
These paragraphs contain the essential points:
The remainder provide proofs or are informational.
History
Before Java 5 introduced
autoboxing/autounboxing,
the addition of two Integer
types would look something like this:
public class Java14Math { public static void main(String[] args) { Integer x = new Integer(1); Integer y = new Integer(2); int _x = x.intValue(); int _y = y.intValue(); Integer xy = new Integer(_x + _y); System.out.println(xy); } }
This code uses an object wrapper type to perform addition and prints the result.
Java 5 autoboxing reduced verbosity by offloading complexity onto the compiler:
Integer a = 1; Integer b = 2; Integer ab = a + b;
As we will see, autoboxing is a mixed blessing.
Josh Bloch said in his Devoxx'11 critique The Evolution of Java: Past, Present, and Future:
[Autoboxing] caused more puzzlers than any other JSR 201 change ... we should have made generics work for primitives instead ... we never should have done this.
I've liberally lifted examples from this and other sources.
The int type
Declaration:
int foo;
foo
is a primitivefoo
stores 32 bits of information (in the rangeInteger.MIN_VALUE
toInteger.MAX_VALUE
)- Literal integers (e.g.
123
or0x7b
) are of typeint
The Integer type
Declaration:
Integer bar;
bar
is an object referencebar
points to an object of typejava.lang.Integer
(or tonull
)- The object
bar
points at has anint
member variable as described above
Choosing between int and Integer
I'll start with how these types should be used before going into detail on why.
- Prefer
int
for performance reasons - Methods that take objects (including generic types like
List<T>
) will implicitly require the use ofInteger
- Use of
Integer
is relatively cheap for low values (-128 to 127) because of interning - useInteger.valueOf(int)
and notnew Integer(int)
- Do not use
==
or!=
withInteger
types - Consider using
Integer
when you need to represent the absence of a value (null
) - Beware unboxing
Integer
values toint
withnull
values
int and Integer are not interchangeable
Type declarations
You can't declare a list of type
List<int>
because generic types must be of type
Object
:
import java.util.Arrays; import java.util.List; public class ListOfNumbers { public static void main(String[] args) { List<Integer> list = Arrays.asList(4, 8, 15, 16, 23, 42); System.out.println(list); } }
Type matching
Beware matching rules when mixing
int
and
Integer
.
Consider this code:
Integer four = 4; List<Integer> nums = new ArrayList<>(asList(4, 8, 15, 16, 23, 42)); nums.remove(four); System.out.println(nums);
It might seem resonable to save a line of code and write:
List<Integer> nums = new ArrayList<>(asList(4, 8, 15, 16, 23, 42)); nums.remove(4); System.out.println(nums);
However, the second snippet invokes a different method.
- boolean List.remove(Object findThis): remove the first object found that equals the argument and return true if found
- Object List.remove(int index): remove the item at the index specified and return it
This is also an example of poor method overloading design - two methods with the same name that do different things.
To match the same method the code could be written as nums.remove(Integer.valueOf(4))
or nums.remove((Integer) 4)
.
Here is a complete listing where the first block removes the value 23
at index 4 while the second removes 4
at index 0:
import static java.util.Arrays.asList; import java.util.ArrayList; import java.util.List; public class ListGotchas { public static void main(String[] args) { { int four = 4; List<Integer> nums = new ArrayList<>(asList(4, 8, 15, 16, 23, 42)); nums.remove(four); // matches List.remove(int) System.out.println(nums); } { Integer four = 4; List<Integer> nums = new ArrayList<>(asList(4, 8, 15, 16, 23, 42)); nums.remove(four); // matches List.remove(Object) System.out.println(nums); } } }
Equality
Here is a listing where the assertEquals
method throws an error if two int
values are not equal:
public class IntEquality { public static void assertEquals(int i1, int i2) { if (i1 != i2) { throw new AssertionError(i1 + " " + i2); } } public static void main(String[] args) { assertEquals(1, 1); assertEquals(1000, 1000); System.out.println("OK"); } }
This code prints OK.
Consider the requirement that the method also support null == null
for Integer
type references.
It is not enough to switch the types:
public class BadIntegerEquality { /** @deprecated method is broken because it uses identity equality */ @Deprecated public static void assertEquals(Integer i1, Integer i2) { if (i1 != i2) { throw new AssertionError(i1 + " " + i2); } } public static void main(String[] args) { assertEquals(1, 1); assertEquals(1000, 1000); System.out.println("OK"); } }
The above code succeeds for assertEquals(1, 1)
but throws the
AssertionError
for assertEquals(1000, 1000)
.
Let's break down what is happening:
- Autoboxing of an
int
literal causes invocation of Integer.valueOf(int) - The value of an
Integer
variable is a memory address - The value
1
lies in the range of values cached byvalueOf
- The value
1000
is not cached and causes the invocation ofnew Integer(1000)
- The two objects containing value
1000
reside at different memory addresses
Simple analogy using two 20 cent coins:
These two coins represent the same value but they are not the same coin.
The value of a variable of type Integer
would be the location of one coin on the table.
The
equals
method must be used to compare the equality of two objects' internal state.
Here is object equality testing with a null
check:
public class IntegerEquality { /** * Equals with null check: * <pre> * if (i1 == null) { * return i2 == null; * } else { * return i1.equals(i2); * } * </pre> */ public static boolean areEqual(Object i1, Object i2) { return (i1 == null) ? (i2 == null) : i1.equals(i2); } public static void assertEquals(Integer i1, Integer i2) { if (!areEqual(i1, i2)) { throw new AssertionError(i1 + " " + i2); } } public static void main(String[] args) { assertEquals(1, 1); assertEquals(1000, 1000); assertEquals(null, null); System.out.println("OK"); } }
Autoboxing and the ternary operator
Caution is required when using the ternary operator with boxed types and null
.
From the language specification:
If one of the second and third operands is of primitive type T, and the type of the other is the result of applying boxing conversion to T, then the type of the conditional expression is T.
That is, for expression types boolean ? int : Integer
or boolean ? Integer : int
the Integer
is always unboxed to int
.
This listing contains a getValueOrZero
method that attempts to get a value from a
thread-safe reference
that may itself be null
:
import java.util.concurrent.atomic.AtomicReference; public class AutoboxingAndTernaryOperator { /** @deprecated throws NPE if ref is empty */ @Deprecated public static int getValueOrZero(AtomicReference<Integer> ref) { Integer stored = (ref == null) ? 0 : ref.get(); return (stored == null) ? 0 : stored; } public static void main(String[] args) { AtomicReference<Integer> empty = new AtomicReference<>(); System.out.println(getValueOrZero(empty)); } }
A NullPointerException
is thrown on the first line if the AtomicReference
contains a null
value.
This can be fixed by changing the expression to (ref == null) ? (Integer) 0 : ref.get();
.
Verification of behaviour by inspection
public class Sums { public static Integer sum(Integer... series) { Integer result = 0; for (Integer i : series) { result += i; } return result; } }
We can inspect the above method using the JDK's javap tool.
public static java.lang.Integer sum(java.lang.Integer...); Code: 0: iconst_0 1: invokestatic #24 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 4: astore_1 5: aload_0 6: dup 7: astore 5 9: arraylength 10: istore 4 12: iconst_0 13: istore_3 14: goto 38 17: aload 5 19: iload_3 20: aaload 21: astore_2 22: aload_1 23: invokevirtual #30 // Method java/lang/Integer.intValue:()I 26: aload_2 27: invokevirtual #30 // Method java/lang/Integer.intValue:()I 30: iadd 31: invokestatic #24 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 34: astore_1 35: iinc 3, 1 38: iload_3 39: iload 4 41: if_icmplt 17 44: aload_1 45: areturn
The results tell us that autoboxing is syntactic sugar hiding method invocations in the compiled code.
Performance
To compare the performance of int
and Integer
I wrote a microbenchmark using
Google's Caliper framework.
Caveats:
- Caliper is still in development and I used a SNAPSHOT build I compiled myself (rev f6ebf866113c.)
- Be aware of the limitations of microbenchmarks
I wrote two implementations of the Adler-32 algorithm:
public class ChecksumDemo { public static int intAdler32(byte[] data) { int a = 1; int b = 0; for (byte element : data) { a = (a + unsignedByte(element)) % 65521; b = (b + a) % 65521; } return (b << 16) | a; } public static Integer integerAdler32(byte[] data) { Integer a = 1; Integer b = 0; for (Byte element : data) { a = (a + unsignedByte(element)) % 65521; b = (b + a) % 65521; } return (b << 16) | a; } private static int unsignedByte(byte b) { return b & 0xFF; } }
Java already has a Adler32 implementation; I just needed an algorithm.
Here are the Caliper results:
PARAMETERS DATA | METHODNAME | RUNTIME (NS) | BYTES (B) | OBJECTS |
---|---|---|---|---|
1 | testInt | 968.937 | 0.000 | 0.000 |
1 | testInteger | 2,154.023 | 2,048.000 | 128.000 |
2 | testInt | 1,947.981 | 0.000 | 0.000 |
2 | testInteger | 4,973.421 | 4,000.000 | 250.000 |
4 | testInt | 3,917.377 | 0.000 | 0.000 |
4 | testInteger | 9,987.991 | 8,192.000 | 512.000 |
From these results we can say that for this benchmark Integer
consumes more time and memory and under similar conditions could add load to garbage collection due to heap (object) allocations.
It is reasonable to prefer int
implementations where possible.
For completeness, here is the benchmark code though I wouldn't be surprised if it fails to compile on a release version of Caliper (it won't work on beta-1 for example.)
import java.nio.charset.StandardCharsets; import java.security.SecureRandom; import com.google.caliper.Benchmark; import com.google.caliper.Param; public class ChecksumBenchmark { @Param({ "1", "2", "4" }) private RandomData data; @Benchmark public int testInt(int reps) { byte[] data = this.data.data; int result = 0; for (int i = 0; i < reps; i++) { result |= ChecksumDemo.intAdler32(data); } return result; } @Benchmark public int testInteger(int reps) { byte[] data = this.data.data; int result = 0; for (int i = 0; i < reps; i++) { result |= ChecksumDemo.integerAdler32(data); } return result; } public static class RandomData { private byte[] data; public static RandomData fromString(String param) { int size = Integer.parseInt(param) * 64; RandomData holder = new RandomData(); holder.data = new byte[size]; new SecureRandom(param.getBytes(StandardCharsets.US_ASCII)) .nextBytes(holder.data); return holder; } } }
No comments:
New comments are not allowed.