McQueeney.com
 

Tom's Blog

Eclipse gave a surprising left jab while unboxing
Published by Tom | February 27, 2007 11:30 PM EST |
I'm working on a project for a client to integrate an existing web-based mortgage application to work with a large mortgage-loan consolidator. The existing application has a large code base originally targeted for Java 1.3. We needed to create an integration API and wanted to take advantage of some of the concurrency classes introduced in Java 1.5. The client gave approval to use 1.5 for the new code.

Upgrading went smoothly. We had to rename an existing package that included the new enum keyword, but the 1.3 code easily upgraded to 1.5. Being able to use 1.5 was nice because I like the generics support, the simplified "for-each" syntax to iterate over collections, and the simplified concurrent package. Many of the new concurrency features also are available for earlier versions of Java in the backport-util-concurrent library. I like Eclipse's support for 1.5, such as typing "foreach[Ctrl-SPACE]" and having Eclipse make a pretty good guess at filling in the simplified for-each loop code, including the collection reference to iterate over, and picking a good name for the temporary iterator variable.

For example, if you start a method:
    public CreditReport getTriMergeForBorrower(
        Borrower borrower, Set creditReports
    ) {
and you want to loop over the creditReports to search for the correct one for this borrower, you can type:
        foreach[Ctrl-SPACE]
and Eclipse will replace that with:
    for (CreditReport report : creditReports) {

    }
Eclipse looks "up" in the code to find the nearest iterable, fills in the correct type for the iteration temporary variable, and gives the temporary variable a reasonable name. Pretty nice. But as the title of this entry implies, I got an unexpected jab from Eclipse, or more accurately, my reliance on it.

Eclipse is aware of another "new" feature in 1.5: autoboxing and unboxing. Autoboxing is the term for the Java compiler allowing you to write code that treats primitives as their equivalent object types (an int treated as if it were a java.lang.Integer, for example). Auto-unboxing is the opposite: using an object when the expression calls for a primitive. If you haven't been able to use Java 1.5 on a project, here's a short introduction.

With autoboxing, you can write code like this (from the above-reference Sun website):
    public static void main(String[] args) {
        Map m = new TreeMap();
        for (String word : args) {
            Integer freq = m.get(word);
            m.put(word, (freq == null ? 1 : freq + 1));
        }
        System.out.println(m);
    }
Notice how it appears you can pass an int as the parameter to be inserted into the TreeMap. Even though you really can't put a primitive into a collection like a map, the compiler (not the Java runtime) corrects this "incorrect" coding by inserting hidden code to create an Integer object to wrap the primitive int value. Autoboxing makes the code look a little cleaner: Let the compiler do the work rather than the programmer.

I've known you could write autoboxing code like the above for a couple of years. In August, 2004, two months before Java 1.5's general release, I attended a talk by Joshua Bloch and Neal Gafter at the Denver Java Users Group, introducing the new features in Java 1.5 (Tiger). But what I hadn't considered was that you sometimes could write auto-unboxing code without realizing it. It happened to me with code like:
    if (creditReport != null && creditReport.isTriMerge()) {
       // do some processing
    }
When testing the code, it threw a NullPointerException. When I saw the stack trace, I said Huh? The creditReport reference obviously isn't null when invoking isTriMerge, so where did the NPE come from? A moment later, it hit me. The isTriMerge method must be returning a Boolean, not a boolean as I had assumed when I looked at the business object's API using Eclipse's Ctrl-SPACE to show options. I let Eclipse fill in the isTriMerge method name when I typed the if statement. Eclipse didn't complain about the syntax, so my natural assumption was a method named "isXXX" would return a boolean.

Instead, the CreditReport business object uses object wrappers for all its primitive-value fields, so when the object is persisted in the database, it can use NULL values to indicate "unspecified." The getters all return objects.

When writing code that takes advantage of unboxing, you can end up creating seemingly odd statements like:
    if (creditReport.isTriMerge() != null &&
        creditReport.isTriMerge()
    ) {
        // Do something
    }
where you check a value for null and whether the value is true. It looks unusual, compared to the pre-Java 1.5 version:
    if (creditReport.isTriMerge() != null &&
        creditReport.isTriMerge().booleanValue
    ) {
        ....
which is what the compiler is actually inserting into the .class file's bytecode.

Unexpected NullPointerExceptions occur when you don't realize that your "primitive" value is really an object being dereferenced inside hidden code. I found this big discussion of automatic unboxing on TheServerSide from three years ago, debating whether unboxing is a good thing.

So, live and learn. I don't see autoboxing/unboxing as bad. Programmers just need to be cognizant of whether a variable holds a reference to a primitive object wrapper rather than a true primitive value, and whether a method returns an object rather than a primitive. And if you want to be cautious, you can ask Eclipse to warn you about possible unboxing problems. Eclipse (I'm using 3.2) has a Java code setting: Window | Preferences | Java | Compiler | Errors/Warnings | Potential programming problems | Boxing and unboxing conversions. You can tell Eclipse to flag boxing/unboxing in the code as a warning or error. That way, you're less likely to receive an unexpected jab from hidden unboxing code. Eclipse's default is to ignore boxing and unboxing in the code as long as it's legal syntax. (I'm sure IDEA and NetBeans can do the same thing. If you're an IDEA or NetBeans user and want to post those IDE equivalents in a comment, please do.)


20070227 Tuesday February 27, 2007 Permalink Comments [4]
Comments:

Rather than having my problems list cluttered with autoboxing warnings, I set the syntax colouring to highlight all autoboxing in red.

Posted by Daniel Campagnoli on February 28, 2007 at 09:06 PM EST #

The solution for that kind of unboxing is making "methods that can return a null value"'s signatures to return the wrapper version. A Boolean, and all these which only return not-null values, return the primitive.

And, of course, when in doubt compare such as:

if(Boolean.TRUE.equals(creditReport.isTriMerge)){
//[...]
}

IMHO these "daangerous" features rely on developers' self organization, it would be a good idea enabling IDEs to "disable" them while coding in order to make these who do not like them happy.

Posted by Fernando on March 08, 2007 at 03:46 AM EST #

Fernando,

Thanks for the comment. The option to change the method to return a boolean rather than a Boolean was unavailable, since I was using someone else's API that I could not change.

Your suggestion to use

if (Boolean.TRUE.equals(creditReport.isTriMerge))

is interesting in that it does highlight the fact that the method is returning the Boolean type. However, it looks a little like using the "if (isTriMerge() == true)" redundancy, and doesn't solve the fundamental issue of not realizing a method is returning an object rather than a primitive. That was my problem here, getting an unexpected NPE because I mistakenly thought I was dealine with a primitive return value rather than an object.

Posted by Tom McQueeney on March 08, 2007 at 12:04 PM EST #

You are right.

It is not a valid solution for the example you are using here. It is a problem for all those who have to "suffer" code written by careless people where autoboxing can lead to that kind of situations.

I was commenting the system I use to avoid suffering that same situation with my code :

If nullable: return a wrapper class
If not nullable: return a basic type

In short, defining method signatures the same as if there were no autoboxing ;).

On the if code. It is what I use for comparing Booleans, that way I can do it in only one step and, as you noted, it makes clear that the returned value is a wrapper.

I hope PMD, checkstyle and all tools alike start detecting these potentially problematic situations soon :).

Posted by Fernando on March 08, 2007 at 12:31 PM EST #

Comments are closed for this entry.