Autoboxing Sucks !
Introduction
Java 1.5 released a lot of features - notable among them were Annotation, Generics, Varargs, for-each loops and an ease-of-coding shortcut called 'Autoboxing'. Autoboxing is a way to automatically convert the primitive types to its corresponding 'Wrapper Classes'. The primary reason why we would like to convert the primitives to a wrapper class is a wrapper class' ability to hold null values. The primitives can never be null. Things were simple before Java 1.5 in the sense that when a programmer needed an object that can hold nulls as well, they would go for the wrapper classes and when they knew that it will always hold a value they would go for a primitive type declaration. One example for this is a bean class that holds a corresponding table value. Some of the values of a table row can be null. For those fields the value is declared as as a wrapper class for the database fields that will always hold a value we usually define them as primitives.How autoboxing works
'Autoboxing' and its inverse 'Unboxing', are implemented by the compiler at compile time. The final compiled class file (bytecode) does not have any information on which objects were autoboxed.
Autoboxing (and unboxing) is a feature which can both be a
boon and a bane to the programmers. This was added in Java 1.5 as a shortcut
for programmers who to covert values from the primitive type to its
corresponding wrapper classes. The decision to Autobox or unbox is made at the
compile time. Autoboxing is, converting the primitive type to its corresponding
wrapper class. Unboxing is converting the Wrapper to its corresponding
primitive. This simple feature provided in Java can cause a lot of coding exceptions
and performance issues, if the programmer is not careful enough.
Have a look at the method below:
private static List<Integer> autoBoxingList(){
List<Integer>
intList = new
ArrayList<Integer>();
for (int i=0; i<20; i++){
intList.add(i);
}
return intList;
}
|
This method just loops through the first 20 numbers
(starting 0) and adds it to a java list. Remember that only objects can be
added to the list and not primitives. What the compiler actually does is that it
converts this code:
intList.add(i);
|
to the one below
intList.add(new Integer(i));
|
The programmer does not have to worry about creating objects
explicitly rather leave it to the compiler. Let’s see another method, this time
for unboxing. The method simply takes in a list of Integers and adds them to
the primitive variable called “sum”. It
basically sums up the Integer values in the List.
private static int unBoxingSum(List<Integer> iList){
int sum = 0;
for (Integer i : iList){
sum += i;
}
return sum;
}
|
The code:
for (Integer i : iList){
sum += i;
}
|
is interpreted by the compiler as:
for (Integer i : iList){
sum += i.intValue();
}
|
Clearly this helps the programmer to write neat code without
worrying about how the conversion or boxing is done. This can lead to many
other unanticipated problems if the programmer is not careful enough, thereby
affecting performance or throwing run time exceptions during execution. Let us go through the problems that you might unexpectedly face while working with autoboxing.
Needless Object Creation
private long foo(){
Long
sum = 0L;
for (long i=0; i<1000; i++) {
sum += i;
}
return sum;
}
|
The method simply adds up first 1000 numbers starting with
0. Of course there is more efficient way to sum a number but that is not the point here. Have a look at the declaration of the
variable “sum” on the first line of the method foo().
Long sum = 0L;
|
Remember that Wrapper classes are “immutable” objects and
any operation which you do, will result in the creation of new objects. This is
how the compiler interprets it.
private void foo (){
Long
sum = new Long(0L); // note this
for (long i = 0; i<1000; i++) {
sum = new Long(sum.longValue() + i); // and this
}
return sum.longValue();
}
|
We are creating objects of “Long” wrapper class,
which was not intended by the programmer. The simple fix would be just a line
change – change “Long” to “long”. Unfortunately, the difference is too small to
notice.
private long foo(){
long
sum = 0L; // changed from Long
wrapper to long primitive
for (long i = 0; i<1000; i++) {
sum += i;
}
return sum;
}
|
It is suggested that you run both the codes and check the
time each take to execute. The one using the “wrapper” would take very long
time, comparatively. A simple rule to remember – don’t use wrapper classes for attributes when you can use the primitive
types. Be extra careful when declaring the attributes with wrapper classes.
It is obvious in the case of Integer or Character as their primitive type
declaration are in lowercase and smaller in length – ‘int’ & ‘char’, but
for ‘Long’ or ‘Boolean’, they can be easily missed.
Recap.
Unboxing Exceptions
If you are thinking that issues happen only when doing
autoboxing and unboxing is safe then have a look at the code below:
public class Test {
private static Integer ii;
private static int i;
private static int j;
public static void main(String... args) {
if (i == j){
System.out.println("Success ...........");
}
if (ii == j){
System.out.println("Success ...........");
}
}
}
|
What do you think the code will print? This code will
compile without even a warning. It would throw a java.lang.NullPointerException
at the comparison “(ii == j)”. This is a common mistake most programmers
make. The mistake of not thinking about the NullPointerException,
unboxing can throw. Especially when a method returns a Wrapper class, the
programmer is not sure if the value returned is null or not. Always make sure that some value is assigned to the
wrapper class before it is being used. Using primitives when declaring the
variable, is the best bet in this case. Here too the same rule as above applies
– use primitives instead of wrappers, as often as possible.
Check for equality carefully
Remember the wrappers are objects, and the comparison with
“==” sign actually compares the equality of the objects rather than the
equality of the values. Look at the code below and guess what values would be
printed on the console.
public class WrapperEquality {
private static Integer wInt1 = new Integer(100);
private static Integer wInt2 = new Integer(100);
private static int pInt = 100;
public static void main(String... args) {
System.out.println(" ============= values =========== ");
System.out.println("wInt1 = " + wInt1);
System.out.println("wInt2 = " + wInt2);
System.out.println("pInt = " + pInt);
System.out.println(" ========== comparisons ========= ");
System.out.println("wInt1 == wInt2 -> " + (wInt1 == wInt2));
System.out.println("wInt1.equals(wInt2) -> " + (wInt1.equals(wInt2)));
System.out.println("wInt1 == pInt -> " + (wInt1 == pInt));
System.out.println("wInt2 == pInt -> " + (wInt2 == pInt));
}
}
|
Did you try out? Check what gets printed on the console.
=============
values ===========
wInt1 = 100
wInt2 = 100
pInt = 100
========== comparisons =========
wInt1 ==
wInt2 -> false
wInt1.equals(wInt2)
-> true
wInt1 == pInt
-> true
wInt2 == pInt -> true
|
I am sure you would have guessed correctly, but only because I prompted you, and you became more careful. In reality it is easy to miss the first one and assume it to be true. The first
comparison, “wInt1 == wInt2 -> false”, not something
we intended. Rather the comparison should have been done using the “equals”
method, which compares the value. How about the primitives? When one of the
elements for comparison is a primitive, compiler does an “unboxing” and then does the equality check.
The code “wInt1 == pInt” is interpreted as
“wInt1.intValue() == pInt”,
so these comparisons are safe (unless we have the Wrappers which are not
initialized, that can give a java.lang.NullPointerException).
The lesson to take home
is, if both are Wrappers, use “equals” method. If one is a primitive, then we
needn’t worry. Better still - always
compare using the primitive value.
wInt1.intValue() == wInt2.intValue()
|
Use Primitives in method parameters and return types
In line with our policy of minimizing the use of wrapper
classes, make sure that any method you write should return primitives rather
than the Wrappers. Stick to this convention as far as possible, and this will
reduce a lot of pain in future. Whenever an object is needed, auto-boxing would
kick in and would help the programmer. It minimizes the risk of null pointers
and needless object creation. Look at the code below, for performance.
public class UsePrimitives {
// bad
private static Integer myInteger() {
return new Integer(100);
}
// good
private static int myInt(){
return 100;
}
public static void main(String... args) {
// good loop
int gSum = 0;
for (int i=0; i<10; i++){
gSum += myInt();
}
// bad loop
int bSum = 0;
for (int i=0; i<10; i++){
bSum += myInteger();
}
System.out.println("Sum=" + gSum);
System.out.println("Sum=" + bSum);
}
}
|
The two private methods, which are being used, can be
thought of as an external API, the programmers would use. These two methods
simple return the number 100, in its primitive form or as a wrapper class.
Looking at the method below:
private static Integer myInteger() {
return new Integer(100);
}
|
If this method is a part of an API, the user does not know how
this return value was achieved, should it be handled for null, etc. Besides,
the Wrapper classes occupy more memory and are treated as Objects. Unless you
need to return a collection, or there is a special need, always send the
primitive type back. It is far more efficient and less error prone. You can get
the Wrapper back whenever you want by assigning the primitive to the Wrapper
class, anyway. In the code above, in 10 iterations the code is 15-25 times more
efficient for a primitive than a wrapper. Of course the way I am using it is
incorrect, and that I should have taken the object in a variable, but then,
this is a demonstration of its efficiency, isn’t it? And we need to give an API as to bad
programmers as well. Should the code be equal in efficiency when I run it only
once? We’ll no, it would be far less efficient even if I run it once. In my
computer, the ratio of execution difference comes between 10 and 20. That is it
is 10 to 20 times less efficient to use a Wrapper.
Not only should you return the values as a primitive type, you
must strive to use primitives as parameters to the methods instead of Wrappers.
This has similar benefits as mentioned above. Even if you return a Wrapper, you
still must strive to write methods with primitive parameters. The codes can
anyway autobox the method which takes in primitive values. Check the code
below. It takes variable arguments for integers and returns the sum of those
integers. (The code is not doing error checking for brevity, but as an API
writer you must do that)
Avoid primitive and wrapper overloading
Overloading a method with primitives and, Wrappers creates
confusion for the programmer as well as the people who use those methods. If
the two methods are spaced out in super and sub classes, even then take extra
care. Use the annotation @Overload whenever you overload any
method (more details under annotation section). Let us look at the three
overloaded methods. Note this is a very bad programming practice, and just
having anyone of these 3, would be sufficient for our purpose.
private static void foo(int int1, int int2){
System.out.println("foo(int int1, int int2). " + (int1 + int2));
}
private static void foo(int int1, Integer int2){
System.out.println("foo(int int1, Integer int2). " + (int1 + int2));
}
private static void foo(Integer int1, Integer int2){
System.out.println("foo(Integer int1, Integer int2). " + (int1 + int2));
}
|
This code compiles perfectly. No issues there. Just
remember, the java compiler would know that the method is overloaded, and would
therefore do the exact match. The below calls would call the first, second and
third methods respectively.
foo(1, 2);
foo(1, new Integer(2));
foo(new Integer(1), new Integer(2));
|
How about the call below? Is this a legal construct, and
which method would be invoked?
foo(new Integer(1), 2);
|
In case of non exact match the compiler would not let you
compile the code for this line because it is unable to resolve the conflict.
The compiler would throw a message such as - The method foo(int, int) is
ambiguous for the type <<Class Name>>. The complier did its part
by not allowing us. We should do our part by not even attempting overloading of
Wrappers and primitives. If however you keep any one of the above three methods
(and comment out other overloaded foo() method, the code will compile
for all the four method calls, as there is no more ambiguity.
foo(1, 2);
foo(1, new Integer(2));
foo(new Integer(1), new Integer(2));
foo(new Integer(1), 2);
|
Conclusion
I personally prefer to use primitives as much as possible for basic operations. This saves memory and has no potential for NullPointerException. It is also more efficient in executing the code. Programmers prefer using Collections rather than arrays, and the Collection supports only object. Many times these problems manifest themselves when you work with objects of Collection. Be extra careful and look for such potential problems when working with Collections including List, Queue, etc. This rule applies to Set and Map too.Recap.
- · Never use Wrapper objects when you can do your work with primitives. Wrapper classes come with the overhead of object creation.
- · Make sure you pay attention for potential null pointer exceptions.
- · Be careful when doing the equality check.
- · Use primitives in method parameters and return types as far as possible.
- · Be careful with method overloading with primitive and wrapper parameter
Comments
Post a Comment