blog.smart-java.nl
Ordina J-Technologies – Java Blog



The strange world of Java autoboxing

By: Roy van Rijn, 19 June 2009

So, you’ve probably been using Java 5 for a while now..? How do you like the new features?

One of the new things is autoboxing. Java is slowly realizing that having primitives wasn’t the best idea. Having only objects instead of primitives (like modern languages, for example Scala) feels more natural and fits OO-programming.

To ease the pain Java introduced this bandage: Autoboxing.

Autoboxing means automatic translation (in some cases) of primitives to the corresponding objects.


So you can do:

int primitiveFifty = 50;
Integer objectFifty = primitiveFifty;



Even better, it allows you to automagically do int-calculations with objects:

Integer calculate = 10;
calculate += 20;



Java translates everything into int, then back to Integer without you noticing anything. This is great! (NOT!)


Autoboxing works great with simple examples like this, but it gets spooky when you use it in combination with widening and narrowing. Let me give you an example:

Byte b = 1;



This is clear, the ‘int’ 1 will be translated without noticing to a Byte. In the following example, the output is too as expected:

Byte b = null;
 
Byte n = 10;
if(false) {
    n = 1;
} else {
    n = b;
}
System.out.println(n);



The result of this piece of code will be ‘null’. Of course, the false will be executed, and then ‘n = b’ will be called. This causes n to be null.


Lets inline this if-then-else to do the same, but shorter:

Byte b = null;
 
Byte n = 10;
n = (false) ? 1 : b;
System.out.println(n);



The code is 100% the same as the example above isn’t it, we only inlined the if…? We do if(false) and the result is either ‘n = 1′ or ‘n = b’…?


Wrong!


When you execute the code you’ll get a NullPointerException!


Why is this? Let us translate the examples back to Java 1.4 code, without using autoboxing:

Byte b = null;
 
Byte n = Byte.valueOf(new Integer(10).byteValue());
if(false) {
    n = Byte.valueOf(new Integer(1).byteValue());
} else {
    n = b;
}
System.out.println(n);



As we can see, Java does a lot to go from int to Byte.

The translation of our second example is less inituative. The following ‘rules’ are from the language specification for the conditional ‘(Operand1 ? Operand2 : Operand2);’.


We have the following code in Java 5: (false) ? (int)1 : (Byte)null);


  • If the second and third operands have the same type (which may be the null type), then that is the type of the conditional expression.
  • If one of the second and third operands is of type boolean and the type of the other is of type Boolean, then the type of the conditional expression is boolean.
  • If one of the second and third operands is of the null type and the type of the other is a reference type, then the type of the conditional expression is that reference type.
  • Otherwise, if the second and third operands have types that are convertible (§5.1.8) to numeric types, then there are several cases:
    • If one of the operands is of type byte or Byte and the other is of type short or Short, then the type of the conditional expression is short.
    • If one of the operands is of type T where T is byte, short, or char, and the other operand is a constant expression of type int whose value is representable in type T, then the type of the conditional expression is T.
    • If one of the operands is of type Byte and the other operand is a constant expression of type int whose value is representable in type byte, then the type of the conditional expression is byte.
    • If one of the operands is of type Short and the other operand is a constant expression of type int whose value is representable in type short, then the type of the conditional expression is short.
    • If one of the operands is of type; Character and the other operand is a constant expression of type int whose value is representable in type char, then the type of the conditional expression is char.
    • Otherwise, binary numeric promotion (§5.6.2) is applied to the operand types, and the type of the conditional expression is the promoted type of the second and third operands. Note that binary numeric promotion performs unboxing conversion (§5.1.8) and value set conversion (§5.1.13).
  • Otherwise, the second and third operands are of types S1 and S2 respectively. Let T1 be the type that results from applying boxing conversion to S1, and let T2 be the type that results from applying boxing conversion to S2. The type of the conditional expression is the result of applying capture conversion (§5.1.10) to lub(T1, T2) (§15.12.2.7).




In our case, the second (int) and third (Byte) are both convertable to numeric types. The third sub-rule applies to our case:


“If one of the operands is of type Byte and the other operand is a constant expression of type int whose value is representable in type byte, then the type of the conditional expression is byte.”


Now we can translate the inline-code to the Java 1.4 variant:

Byte b = null;
Byte n = new Byte((byte)10);
n = Byte.valueOf((false)? (byte)1 : b.byteValue());
 
System.out.println(n);



As you can see, Java tries to get the byte value in both cases. To do this on b, it needs to call .byteValue(), which results in a NullPointerException…


So how do you safely inline it? You have to make sure the two values in the conditional have the same type as the type you expect, in our case:

Byte b = null;
Byte n = 10;
 
//Make sure we have Byte = ()?Byte:Byte;
n = (false)? (Byte)(byte)1 : b; 
 
System.out.println(n);



So, be alert when using autoboxing and widening/narrowing in the same piece of code. Happy coding!

3 reacties op “The strange world of Java autoboxing”

  1. Jan-Kees van Andel zegt:

    There are tons of risks when you start combining primitives and wrappers. For example, check the following code:

    long time = System.currentTimeMillis();
    Long total = 0L;
    for (int i = 0; i < 1000000000; i++) {
        Integer i2 = i;
        total += i2;
    }
    System.out.println("Total: " + total + ", time: " + (System.currentTimeMillis() - time) + "ms");

    Rewrite it to this and performance increases dramatically:

    long total = 0L;
    for (int i = 0; i < 1000000000; i++) {
        total += i;
    }
    System.out.println("Total: " + total + ", time: " + (System.currentTimeMillis() - time) + "ms");

    This is the output on my machine:
    Total: 499999999500000000, time: 23742ms
    Total: 499999999500000000, time: 687ms
    Total: 499999999500000000, time: 23236ms
    Total: 499999999500000000, time: 681ms

    Quite a difference, not?

    The following is also funny:

    public static Boolean test() {
        return null;
    }
     
    public static void main(String[] args) {
        if (test()) { // NPE
            System.out.println("Yay!");
        }
    }

    Luckily, we’ve got tooling to warn us about these mistakes. For example, FindBugs: http://findbugs.sourceforge.net/bugDescriptions.html#NP_BOOLEAN_RETURN_NULL

  2. Peter Schuler zegt:

    It’s a big issue that primitives don’t have a null value. Thus they can’t express a “no-set” option. This is especially a problem with booleans. A value of false doens’t mean that the value has explicitly set by the user.

    I try to avoid the primitive types as much as possible. Most certainly for class variables.

  3. Vincent zegt:

    Peter, a boolean that can have 3 values – true, false and null – is not really a boolean, now is it? ;-)

    What you should be doing instead of avoiding primitive types, is avoid the use of null completely. Write contracts. Specify invariants, preconditions and postconditions. Almost always there is no good reason to use null values. If you disallow null, code gets a lot simpler. And you safely can – and should – use primitives.

    See, for example, JSR 305 (still in progress).

    Additionally, design for immutability. Use final members. See Effective Java, 2nd Edition, Item 15 (Minimize mutability).

    Sure, primitives in Java are open for debate. There have been lots of discussions about them. That in a true OO-language, you shouldn’t have primitives (Smalltalk, Ruby, Scala). That even without the primitives, Java could be just as fast because of the JIT compiler. And so on. But the fact is that primitives are there. Using them is okay! Especially when you don’t want null values, and do want immutability.

    Vincent

Laat een reactie achter