Wednesday, August 4, 2021

What is Autoboxing and Unboxing in Java ? Example, Tutorial and Corner cases

What is Autoboxing in Java
Autoboxing and unboxing are introduced in Java 1.5 to automatically convert the primitive type into boxed primitive( Object or Wrapper class). autoboxing allows you to use primitive and object types interchangeably in Java in many places like an assignment, method invocation, etc. If you have been using Collections like HashMap or ArrayList before Java 1.5 then you are familiar with the issues like you can not directly put primitives into Collections, instead, you first need to convert them into Object only then only you can put them into Collections

Wrapper classes like Integer, Double and Boolean help for converting primitive to Object but that clutter the code. With the introduction of autoboxing and unboxing in Java, this primitive to object conversion happens automatically by the Java compiler which makes the code more readable.

But both autoboxing and unboxing come with certain caveats which need to be understood before using them in production code and it becomes even more important because they are automatic and can create subtle bugs if you are not sure when autoboxing in Java code occurs and when unboxing happens.

This is my fifth article on features introduced in Java 5 after my post on Java EnumHow Generics works in Java, and varargs example. In this Java tutorial, we will see: What is autoboxing and unboxing in Java?  When autoboxing and unboxing occur in Java? and things to remember while dealing with primitives and objects in Java with code examples.





What is autoboxing and unboxing in Java?

What is auboxing means in Java with ExampleWhen Java automatically converts a primitive type like int into a corresponding wrapper class object e.g. Integer then it's called autoboxing because primitive is boxed into wrapper class while in the opposite case is called unboxing, where an Integer object is converted into a primitive int. 

All primitive types like byte, short, char, int, long, float, double and boolean have corresponding wrapper classes like Byte, Short, Integer, Character, etc and participate in autoboxing and unboxing. Since the whole process happens automatically without writing any code for conversion it's called autoboxing and auto-unboxing.

Autoboxing in Java


An important point about Autoboxing and Unboxing in Java

1) Compiler uses valueOf() method to convert primitive to Object and uses intValue(), doubleValue() etc to get primitive value from Object.
2)  During autoboxing boolean is converted to Boolean, byte to Byte, char converted to Character, float changes to Float, int goes to Integer, long goes to Long and short converts to Short, while in unboxing opposite happens like Float to float.

If you want to understand all the features introduced in Java 5 in much more detail, then I suggest looking at the Core Java book by Cay S. Horstmann, one of the best core Java books, which covers both concurrency and general features well.

Autoboxing and unboxing in Java


When do autoboxing and unboxing occur in Java?

Autoboxing and unboxing can happen anywhere where an object is expected and primitive type is available for example In method invocation where an object argument is expected,  if you pass primitive, Java automatically converts primitive into equal value Object

The classic use of autoboxing is adding primitive types into Collection like ArrayList in Java or creating an instance of parameterized classes e.g. ThreadLocal which expects Type. here is some code example of autoboxing and unboxing in Java:

ArrayList<Integer> intList = new ArrayList<Integer>();
intList.add(1); //autoboxing - primitive to object
intList.add(2); //autoboxing
     
ThreadLocal<Integer> intLocal = new ThreadLocal<Integer>();
intLocal.set(4); //autoboxing

int number = intList.get(0); // unboxing
int local = intLocal.get(); // unboxing in Java

You can find all places by applying some common sense as well, just see if an object is needed or a primitive type and what is available there but don’t confuse between widening and autoboxing, where formerly refers to promoting small type into bigger type wherever expected e.g. converting a byte to int. I have shared a couple of conversion tutorials in java like String to int conversion and  Double to String conversion if you like you also check those.


Autoboxing and Unboxing Example in Java

In the last section we discussed What is autoboxing and unboxing in Java and when do they occur. In short, Autoboxing mainly occur in two places one is during the assignment and other is during method invocation, let’s see a couple of example of autoboxing and unboxing in Java to understand it better :

Autoboxing and Unboxing Example in Java


Autoboxing and unboxing in the assignment:

This is the most common example of autoboxing in Java, earlier the code was bloated with the explicit conversion which is now taken care of by the compiler.

//before autoboxing
Integer iObject = Integer.valueOf(3);
Int iPrimitive = iObject.intValue()

//after java5
Integer iObject = 3; //autobxing - primitive to wrapper conversion
int iPrimitive = iObject; //unboxing - object to primitive conversion



Autoboxing and unboxing in method invocation:

This is another place where autoboxing makes your life easy, it allows you to pass Object or primitive interchangeably in a method without explicit conversion:

public static Integer show(Integer iParam){
   System.out.println("autoboxing example - method invocation i: " + iParam);
   return iParam;
}

//autoboxing and unboxing in method invocation
show(3); //autoboxing
int result = show(3); //unboxing because return type of method is Integer

When we call the show(Integer) method which accepts the Integer object with primitive int autoboxing will first convert primitive to object and then call the show() method. On the second line, unboxing happens because the show() method returns Integer while returned value is stored in primitive int variable result.



Unnecessary Object creation due to Autoboxing in Java

One of the dangers of autoboxing is throw away object which gets created if autoboxing occurs in a loop. Here is an example of how unnecessary object can slow down your application :

 Integer sum = 0;
 for(int i=1000; i<5000; i++){
   sum+=i;
 }

In this code sum+=i will expand as sum = sum + i and since + operator is not applicable to Integer object it will trigger unboxing of sum Integer object and then autoboxing of result which will be stored in sum which is Integer as shown below :

sum = sum.intValue() + i;
Integer sum = new Integer(result);      
     
here since the sum is Integer, it will create around 4000 unnecessary Integer object which are just throw away and if this happens on a large scale has It potential to slow down system with frequent GC for arithmetic calculation always prefer primitive over boxed primitive and look for unintentional autoboxing in Java



Autoboxing and method overloading in Java

autoboxing has complicated method overloading in Java, prior to Java 1.5 value(int) and value(Integer) were completely different and there was no confusion which method will be called based on the type of argument e.g. if you pass int first method will be called and if you pass Integer second method will be called.

With autoboxing and unboxing in place, it gets trickier. a classic example of this is ArrayList remove()  method which is overloaded i.e. remove(index) and remove(Object), Since now ArrayList has two remove() methods autoboxing will not occur and the respective method will get called as shown in the below example of overloading with autoboxing in Java.

public void test(int num){
    System.out.println("method with primitive argument");
             
}
 
public void test(Integer num){
    System.out.println("method with wrapper argument");
             
}

//calling overloaded method
AutoboxingTest autoTest = new AutoboxingTest();
int value = 3;
autoTest.test(value); //no autoboxing
Integer iValue = value;
autoTest.test(iValue); //no autoboxing

Output:
the method with a primitive argument
the method with wrapper argument
      

Things to remember while using autoboxing in Java

So far we have seen What is autoboxing means in Java, What is unboxing in Java and when does it occur, But every powerful feature comes with some caveats and corner cases, here are a few which is worth remembering while using auto-boxing in Java:

1. Comparing Objects with equality Operator
I agree that autoboxing of primitive to Object  adds a lot of conveniences and reduces verbosity but there are few places where autoboxing is error-prone e.g. equality operator "==". Since equality operator can be applied on both primitive and Objects it leads to confusion and can cause subtle issues.

When you compare two objects using the "==" operator it compares the object's identity and not value and also no auto boxing occurs. By the way, it's not best practice to use equality operator to compare Objects, use the equals method instead. here is an example that makes it clear :

Integer one = new Integer(1);
Integer anotherOne = new Integer(1);
     
if(one == anotherOne){
  System.out.println("both one are equal");
         
}else{
   System.out.println("Both one are not equal");
}

It will print "Both ones are not equal" because of no autoboxing. Things get more confusing when "==" comparison is combined with other logical operators like > and < which does auto-unboxing before comparison. This one is explained beautifully with an example of Comparator in Effective Java if you haven't read then go get a copy.

One of my readers Mitchee says that it's not clear, so I am updating this section with few more details, Mitchee, let me know if it makes sense:

public class AutoboxingTest {

    public static void main(String args[]) {

        // Example 1: == comparison pure primitive – no autoboxing
        int i1 = 1;
        int i2 = 1;
        System.out.println("i1==i2 : " + (i1 == i2)); // true

        // Example 2: equality operator mixing object and primitive
        Integer num1 = 1; // autoboxing
        int num2 = 1;
        System.out.println("num1 == num2 : " + (num1 == num2)); // true

        // Example 3: special case - arises due to autoboxing in Java
        Integer obj1 = 1; // autoboxing will call Integer.valueOf()
        Integer obj2 = 1; // same call to Integer.valueOf() will return same
                            // cached Object

        System.out.println("obj1 == obj2 : " + (obj1 == obj2)); // true

        // Example 4: equality operator - pure object comparison
        Integer one = new Integer(1); // no autoboxing
        Integer anotherOne = new Integer(1);
        System.out.println("one == anotherOne : " + (one == anotherOne)); // false

    }

}

Output:
i1==i2 : true
num1 == num2 : true
obj1 == obj2 : true
one == anotherOne : false

In the first example, both arguments of == operator are primitive int type so no autoboxing occurs and since 1==1 it prints true, While in the second example during an assignment to num1, autoboxing occurs which converts primitive 1 into Integer(1) and when we compare num1==num2 unboxing occurs and Integer(1) is converted back to 1 by calling Integer.intValue() method and since 1==1 result is true.

In the third example which is a corner case in autoboxing, both Integer objects are initialized automatically due to autoboxing and since Integer.valueOf() method is used to convert int to Integer and it caches object ranges from -128 to 127, it returns the same object both time.

In short obj1 and obj2 are pointing to the same object and when we compare two objects with a == operator it returns true without any autoboxing. In the last example object is explicitly initialized and compared using equality operator, this time, == return false because both one and anotherOne reference variables are pointing to the different object.


2. Mixing object and primitive in equality and relational operator
Another mistake to avoid while using autoboxing and unboxing in Java is mixing primitive and Object in equality or relational operator much like mixing static and non-static synchronized methods. if we compare one primitive with another object then unboxing of the object is occur which could throw NullPointerException if the object is null e.g.

private static Integer count;

//NullPointerException on unboxing
if( count <= 0){
  System.out.println("Count is not started yet");
}


3. Cached Objects
One more caveat or danger of autoboxing and unboxing is a cached object since valueOf() is used to create boxed primitive and it caches frequently used Object which may behave differently based upon their value as Java only cache integers from -128 to 128.  I have discussed this problem in detail in the post What is wrong while using "==" with autoboxing in Java.


4. Unnecessary objects and GC overhead
Last but not least is the cost associate with autoboxing and unboxing. Since autoboxing creates an unnecessary object and if that goes beyond a limit usually outside the range of cached value it can potentially slow your program by frequently causing garbage collection.


In Summary autoboxing and unboxing in Java are a great convenience but demands care and awareness while using them. autoboxing and unboxing have several legitimate use cases but should not be used with equality operators especially mixing with primitive and object are dangerous. 

If you like to read books check out Effective Java and Java 5.0 Tiger: A Developer's Notebook, those have some more insightful tips on autoboxing and unboxing in Java.


Other Java tutorials from this blog

16 comments:

  1. Boxing was introduced in Java after C# had included it. This is a nice feature for development but, as you stated, not to be overlooked in deployment due to performance issues i.e. too much memory being consumed. Another issue, along the same lines is String versus StringBuffer. The append method of the latter really helps to cut down on the memory consumption and duplication process that occurs when adding strings to achieve the same outcome.

    ReplyDelete
  2. i don't understand:
    ' It will print "Both one are not equal" because of no auto boxing" '
    makes no sense to me....

    ReplyDelete
  3. what if one of the members of == is a primitive and the other an object? Is there any auto-un/boxing?

    ReplyDelete
  4. Hi Michee, I have updated autoboxing article to make that point more explicit. On your second question if one of the member is primitive than unboxing of other member is occur.

    Javin

    ReplyDelete
  5. i still don't get it....What's the point in talking about 'autoboxing' when you already have 2 objects (not primitives) and you compare them via equal?

    ReplyDelete
  6. Ahh, Sorry for extra 't' :), michee whole point is to stress that avoid autoboxing while using == operator because it can confuse you if you are using it carelessly.

    ReplyDelete
  7. Np.you say there:

    'It will print "Both one are not equal" because of no autoboxing'

    but they are already objects....so perhaps you ment autounboxing....or something else.

    ReplyDelete
  8. Yes you are right, since both variables are pointing to object it was simple Object comparison without any autoboxing.

    ReplyDelete
  9. Thanks for the insight on autoboxing & unboxing..

    ReplyDelete
  10. One of the worst thing about Autoboxing in Java is NullPointerException. If you have method which returns int by converting Integer to int, then it throws NullPointerException, when corresponding integer is null. It would have been much better to return zero rather than throwing exception. here is an example to watch

    public int getCount(String key){
    Integer value = cache.get(key);
    return value;
    }

    This method will throw NPE, if value is null, frustrating.

    ReplyDelete
  11. Hi Javin

    Can you plz explain why it is true in 1st case ?
    Integer i = new Integer(1);
    Integer j = new Integer(1);
    System.out.println("Print value "+(i <= j && i >= j && i != j) ); //true
    System.out.println("Print value "+(i < j && i > j && i != j) ); //false

    ReplyDelete
  12. Hi Javin,
    Confirm, if autoboxing and unboxing are compile time process, rather than run time.

    ReplyDelete
  13. sum = sum.intValue() + i;
    Integer sum = new Integer(result);

    - Based on the javadoc, i think it would be something like (difference is valueOf caches the frequently used value)

    sum = sum.intValue() + i;
    Integer sum = Integer.valueOf(result);

    ReplyDelete
  14. int x = 123;

    long y = x;

    In the above cases autoboxing/unboxing happens ?

    ReplyDelete
  15. Your 1) Comparing Objects with equality Operator example is not appropriate in this context.
    we already know comparing two objects(using == operator) created using new operator will never evaluate to true. Instead you should use something like this :

    int a = 1;
    int b = 1;
    compareInt(a,b);


    private static void compareInt(Integer a, Integer b) {

    if(a == b){
    System.out.println("a == b");
    }
    else{
    System.out.println("a != b");
    }

    }

    ReplyDelete
  16. How java provide the facility about autoboxing?

    ReplyDelete