Alan has posted 1 posts at DZone. View Full User Profile

Why I'm Not a Fan Of Java's Auto-Unboxing

07.09.2008
| 12820 views |
  • submit to reddit

Starting with Java 1.5, the Java compiler started automatically taking care of converting boxed versions of primitive types into primitive types where necessary, and vice-versa.  While I’m generally in favor of anything that makes programming languages easier to use, provided it doesn’t overly-complicate things, I’ve never been a huge fan of the way it was done.

Most languages either have C or C++ style primitives or object-style numbers and booleans, but Java has always had an odd mix of the two.  The easiest thing for a programmer is to simply go all the way to the object model and be done with it; my assumption is that Java didn’t do that both for the sake of continuity with C and for the sake of raw performance.

But unfortunately, the solution of auto-converting between the two worlds doesn’t really solve the problem that there are differences there that you have to be aware of.  The main difference is around null; a primitive type can’t be null, and a boxed type can, so every time you unbox an object you have to worry about how to handle null values.  If you do the unboxing by hand, at least you might have to think about it, and if you don’t at least it’ll be obvious what the error is.  But the auto-unboxing both manages to not handle null for you and manages to completely hide the errors when they do happen, which is basically a huge lose-lose in my book.  I managed to spend far too long the other day trying to figure out why the following line of code was throwing an NPE:

modifier.setEligible(!pattern.getDisplayEligibility());

My instinct is that an NPE is caused by a method call or field reference on a null object, so clearly either “modifier” or “pattern” has to be null here, right?  So after staring hard at the code for several minutes to try to figure out how that could be possible I had to go to the trouble of setting everything up in the debugger, walking through the code . . . and finding out neither was null.  Huh?  Of course, not at all obvious from reading the code is the fact that getDisplayEligibility() returns a Boolean rather than a boolean, and in this case it was returning null, meaning that the ! operator was throwing the NPE.

Normally, if you tried to apply ! to an object you’d get a compile error, but thanks to the magic of auto-unboxing Java now has all sorts of exciting ways to throw NPEs that you wouldn’t think to find.  Right after fixing that bug I ran into another, very similar error:

public boolean isScheduleRate() {
  return getPattern().getScheduleRate();
}

Again, getScheduleRate() actually returns a Boolean instead of a boolean, so if it’s null the return statement will NPE.  Combined with the way Java implemented the new for loop, that means that instead of an NPE only being possible on the . operator or [] references, you now have to look out for !, return, arithmetic operators like + and -, and for loops.

In GScript we, for better or worse, auto-coerce null to false or 0 depending on the context, which also requires you to understand that that can happen, but at least prevents the sort of hidden NPEs that the current Java approach causes.  It probably behaves like you’d expect most of the time and is generally consistent with how other languages behave.

Published at DZone with permission of its author, Alan Keefer.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Tags:

Comments

Slava Imeshev replied on Wed, 2008/07/09 - 1:45am

 

I hit this one a couple of times. The only solution to this problem may lay in defensive programming where you validate the input as the first thing in execution flow. "Trust No one" (c) XFiles.

In the example you gave the real issue is that displayEligibility is null when it is returned from the method. So, your code blows up with an NPE as it should.

That null value has gotten there somehow. Code that exercises the defensive programming would check if the value is null when a setter (if any) is called and throw an exception or set the displayEligibility to a default value that matches logic for the null value. Also, it should set the default value in the constructor:

 

Variant with aggressive validation:

class Pattern  {

private displayEligibility = false;

void setDisplayEligibility(final boolean displayEligibility) {

if (displayEligibility == null) {
throw IllegalArgumentException("Display eligibility cannot be null");
}

this.displayEligibility = displayEligibility;
}

 

Variant with default (less preference because it hides null value):

 

class Pattern  {

private displayEligibility = false;

void setDisplayEligibility(final boolean displayEligibility) {

this.displayEligibility = displayEligibility == null ? false : displayEligibility ;
}

 

Hope this helps.

 

Regards,

Slava Imeshev

Cacheonix: Clustered Cache for Java

 

 

J Szy replied on Wed, 2008/07/09 - 2:14am

It is possible to setup eclipse to issue a warning whenever you use autoboxing or autounboxing. Unfortunately (as of Eclipse 3.3) it is NOT possible to warn only on autounboxing. :(

Pete Cox replied on Wed, 2008/07/09 - 2:15am

For Boolean one can always use the equals() method e.g.

import static java.lang.Boolean.TRUE; 
 ...
 Object booleanValue;

...

if (TRUE.equals(booleanValue)){

   // true!

}

Alan Keefer replied on Wed, 2008/07/09 - 2:54am

Indeed, and Boolean.TRUE.equals() or Boolean.FALSE.equals() is usually what I end up using.  The problem is that the auto-unboxing is fairly useless, because the only times you can use it are when you're 100% sure that the value is never going to be null.  Since that's generally not the case, and it's certainly not something you can enforce via the compiler in Java, you kind of have to always do the unboxing yourself and deal with null in the appropriate fashion.  Basically the only times I end up using unboxing are when I do so accidentally, and I usually find out that I've done so because I get an NPE in a seemingly impossible place.  The end result is that auto-unboxing makes it easier to make mistakes than it was in Java 1.4 and prior (since then at least !someBooleanMethod() was a compile error instead of an almost-certainly-latent runtime error) and does nothing to help me write correct code, which seems like a step backwards for the language.

Howard Lovatt replied on Wed, 2008/07/09 - 3:13am

If BGGA comes along we get more boxing; closures get boxed to interfaces, but stragely: only interfaces not classes, only interfaces with one method, sometimes the interface needs to extend RestrictedClosure, and there is no autounboxing.

Bob Morlaix replied on Wed, 2008/07/09 - 3:27am

What seems weird to me, is that the two methods of your example that are supposed to return Boolean values may return null... IMHO, methods that returns boxed values (i.e. Integer, Float, Boolean, ...) should never return null. What does it mean when a method that is supposed to return a Boolean value returns null? It doesn't mean "true", it doesn't mean "false"... Does it mean "maybe"?...

J Szy replied on Wed, 2008/07/09 - 5:40am

I thought about it for a while and I can't come up with an example where an inadvertent autoboxing conversion would make a program fail at runtime or yield an incorrect result. Is it possible at all, or can autoboxing harm only performance, but not correctness?

El Barsum replied on Wed, 2008/07/09 - 9:08am

Article -> EXACTLY! Just my thoughts!

Manrico Corazzi replied on Wed, 2008/07/09 - 9:18am in response to: Bob Morlaix

[quote=BobMorlaix]What seems weird to me, is that the two methods of your example that are supposed to return Boolean values may return null... [/quote]

Weird indeed. What about a Null Object Pattern?

I can't make up my mind about boxing/unboxing. It seems like another of those things you come up at a certain point in the life of a programming language because you realize you screwed up something at the beginning; but I've got to admit that - altough not perfect - this feature might prove useful (provided that you use it wisely and knowingly, but that can be said about almost everything, can't it?).

El Barsum replied on Wed, 2008/07/09 - 9:20am in response to: Bob Morlaix

It means 'undefined' - examples are numerous.

 

 Map<String, Boolean> booleanMap = new HashMap<String, Boolean>();

 booleanMap .get("Are you there?");

 

Sure you could enforce "Null Object pattern" in your code, but not everyone does.

Peter Ufak replied on Wed, 2008/07/09 - 10:27am

I hate autoboxing. You throw up type control and checking. If there is autoboxing used somewhere something is wrong (API?). If you do not need type control use other language. 

Alex(JAlexoid) ... replied on Wed, 2008/07/09 - 10:28am

My personal favourite rule for using autoboxed values is: Never use anything that can be autoboxed in synchronization schemes.

Paul HOule replied on Wed, 2008/07/09 - 10:30am

@Slava,

      I'm not a fan of manual null checks.  When you go down that road you can end up writing 15 lines of null-checking code to protect one line of actual functionality.

      There are times that you need to write manual null checks,  but I see them as a bad smell.  It's not always obvious what the right thing to do is when you write a null check.  A program could contain hundreds of null checks,  and even if you only do the wrong thing 5% of the time,  you can introduce tens of latent defects in your programs.

      I think it's imperative for programming styles to minimize the burden of null handling.  Unfortunately,  today's generation of languages doesn't really help us.

David Karr replied on Wed, 2008/07/09 - 10:46am in response to: Slava Imeshev

I'm guessing you didn't compile this code?

Primitives can't be null.

Alan Keefer replied on Wed, 2008/07/09 - 11:41am in response to: Manrico Corazzi

I don't think the Null Object Pattern is really possible here; you can't go around creating other Boolean values, so you're stuck with either TRUE or FALSE.  But setting the value to TRUE or FALSE is very different from leaving it as null; in our case, most of the time we deal with Boolean values instead of boolean values it's because the value is coming from either a database column or a configuration file.  In both cases null means "the user hasn't made a choice yet" and it's often important to capture that information instead of setting a default.  When evaluating a conditional you then need to decide if null means true or false for that value based on the default handling for that field.  I usually do that in the data object itself instead of exposing the Boolean value to anyone, but it's easy to forget to do that occassionally because it won't generate a compiler error.

Slava Imeshev replied on Wed, 2008/07/09 - 5:02pm in response to: Paul HOule

> I'm not a fan of manual null checks. When you go down that road you

> can end up writing 15 lines of null-checking code to protect one line of actual functionality.

 

Nope, it is a single-line check:

 

ParamUtils.validateNotNull(myParameter, "account number");

 

There is no such thing as too much protection.

 

Regards,

Slava Imeshev

Cacheonix: Coherent Clustered Java Cache

 

 

Slava Imeshev replied on Wed, 2008/07/09 - 3:53pm in response to: David Karr

[quote=dk52519]

I'm guessing you didn't compile this code?

Primitives can't be null.

[/quote]

 

Sure I didn't. This is just to illustrate the idea. That null Boolean that tried to unbox came from somewhere. At that point it should have been checked for null and appropriate measures should be taken.

Without the explicit checks any code will throw an NPE, boxed or not. Autoboxing just makes you scratch your head first time. After that you do checks and the problem goes away.

Regards, 

Slava Imeshev

 

John Kelvie replied on Wed, 2008/07/09 - 5:10pm

 I believe this is criticism is unfair, and to the extent it is valid, is not directed at the author's real problem.

 Autboxing and unboxing is useful because it allows for less verbose code. This is helpful.

 The choice to have them throw nullpointerexceptions is consistent with Java's strong type-safety at compilation as well as runtime. Like this or not(currently this seems to have fallen out of fashion, but it shouldn't be so glibly dismissed), to diverge from this design philosophy on this one feature would be a poor design choice (consistency matters in design). 

Even if they did have a choice in the matter, defaulting booleans to false is a very, very questionable design decision. It could lead to far more insidious bugs than simple nullpointerexceptions (ones that I could easily see programmers making). Its not reasonable to assume that a value that is not set is the same is false, and in many applications null in a data model value has a much differnt meaning than false. How nulls should be handled to me is most definitely the concern of the programmer.

 The only real alternative I would say is to throw no error at all (as some languages such as Objective-C do in the case of passing messages to null objects) and skip the statement.

Either way, if one does have problem with auto-unboxes throwing NPEs, then I would suggest the problem is actually with nullpointers overall and the general approach java has to type safety. I think Java's approach is a valid one, and one that I find perfectly easy to program with. Other languages adopt different approachs, and those also can be successful for development. Either way, I don't think doing an overall comparison of runtime safety approaches is appropriate in the context of this reply, and I would leave it at this being an appropriate and useful addition to the Java programming language.

 Any replies can be added here, or on my blog http://www.lightheadcode.com

 -John

 

 

Alan Keefer replied on Wed, 2008/07/09 - 6:06pm in response to: John Kelvie

I still have yet to have anyone say why auto-unboxing is useful, though:  how does it help me write less verbose code?  I have to handle the null-check myself anyway, so my Java code with auto-unboxing should look basically exactly like my Java code without auto-unboxing.  But it does introduce a possibility for non-obvious errors that weren't there before.  So how is that an improvement?  Why even bother putting auto-unboxing in?

John Kelvie replied on Wed, 2008/07/09 - 6:27pm in response to: Alan Keefer

You are correct about still having to do NullPointer checks. I see that as an inevitable result of how Java approaches NullPointer handling, as I mentioned above. Even where nullpointer checks are necessary though, the syntax is shortened in that you can say:

if (intValue == null) {

     o.setIntValue(intValue);

)

as opposed to:

if (intValue == null) {

     o.setIntValue(intValue.intValue());

}

How much of a savings is this? Well, not that much. But I do appreciate it and I miss it when working with other languages (such as Objective-C) where there are both primitives and value types and the feature does not exist.

 It does add some complexity and the possibility for a type of error that did not exist before. I think it's worth it, but this is certainly a drawback. I think it's worth noting that the .NET implementation is almost identical, down to the exception throwing in the case of the value-type variable being null.

 -John

Slava Imeshev replied on Wed, 2008/07/09 - 6:24pm in response to: Alan Keefer

[quote=akeefer]

I still have yet to have anyone say why auto-unboxing is useful, though: how does it help me write less verbose code? I have to handle the null-check myself anyway, so my Java code with auto-unboxing should look basically exactly like my Java code without auto-unboxing. But it does introduce a possibility for non-obvious errors that weren't there before. So how is that an improvement? Why even bother putting auto-unboxing in?

[/quote]

 

1. The main benefit of autoboxing is less verbose code, obvisously.

2. Autoboxing never promised doing null checks for you (that you should have been doing it anyway).

3. It does not introduce non-obvious errors. The problem is that original Boolean was let wonder around w/o being set. Your original code would blow up just fine if there were no autoboxing and you used explicit unboxing instead.

4. Running static code analysys as a part of the pre-check-in build may help to trap such errors.

 

Regards,

Slava Imeshev

 

 

 

Alan Keefer replied on Wed, 2008/07/09 - 6:32pm in response to: Slava Imeshev

[quote=Slave Imeshev]1. The main  benefit of autoboxing is less verbose code,  obvisously.  
. . .
3. It does not introduce non-obvious errors. The problem is that
original Boolean was let wonder around w/o being set. Your original
code would blow up just fine if there were no autoboxing and you used
explicit unboxing instead.[/quote]
 

Auto-boxing makes code less verbose, but auto-unboxing doesn't; as I pointed out, you have to write the null-checks each way, so the code is exactly the same in either case and thus is neither more nor less verbose.

And it doesn't introduce the errors with not checking for null, what it does do is hide them.  My original code wouldn't have blown up at runtime under Java 1.4; it wouldn't have compiled in the first place, so it would have never made it into the system at all.

I agree that leveraging the IDE to do checks for auto-unboxing is helpful, it just seems odd that I have to use my IDE to essentially un-do a feature added to the language (i.e. to turn those back into compile errors)

Slava Imeshev replied on Wed, 2008/07/09 - 6:39pm in response to: Alan Keefer

[quote=akeefer]

And it doesn't introduce the errors with not checking for null, what it does do is hide them. My original code wouldn't have blown up at runtime under Java 1.4; it wouldn't have compiled in the first place, so it would have never made it into the system at all. 

[/quote]

 

I think you have to show us how 1.4 would not compile. 

 

Slava 

Alan Keefer replied on Wed, 2008/07/09 - 6:43pm

In Java 1.4, the line:

modifier.setEligible(!pattern.getDisplayEligibility()); 

wouldn't compile, since getDisplayEligibility() returns a Boolean.  So I'd be forced to handle the potential null condition right then when I wrote the code, either by changing the method to return a boolean and handling it internally or by putting the null check in here.  Either way, I'd have to deal with it at compile time.

In Java 1.5, that line compiles but it's a runtime error if getDisplayEligibility() returns null.  So the auto-unboxing has hidden an error that was caught at compile time in 1.4 but which only shows up at runtime in 1.5, which seems like a step backwards.

Slava Imeshev replied on Wed, 2008/07/09 - 6:56pm in response to: Alan Keefer

[quote=akeefer]

So I'd be forced to handle the potential null condition right then when I wrote the code.

[/quote]

 

No, you would not be forced. What you could do is this:

 

modifier.setEligible(!pattern.getDisplayEligibility().booleanValue());

 

and it would blow up just like before, and nothing a compiler could do here.

But, if you did do check, nothing would prevent you from doing check in the original code, but much earlier, when the null Boolean was introduced.

 

At any rate, the problem here is that the boolean method returns null while it should not. This is not autoboxing fault. It is a bug.

 

 

Slava

Alan Keefer replied on Wed, 2008/07/09 - 7:01pm in response to: Slava Imeshev

Er, but that's not what I wrote.  If I'd had to type ".booleanValue()" I would have been clued in to the fact that there was a possible NPE lurking there that I should guard against.  I might have made a bad choice, but at least I'd have been aware that I was making a choice.  My point is that the code as I wrote it had a bug that would be caught at compile time in 1.4 but only shows up at runtime in 1.5.  There are a million different ways to check for null, to convert Boolean to boolean, etc. but the bug only showed up because I didn't realize I needed to do that in the first place because, from just looking at the code (or typing it quickly and seeing it compile) it's not clear there needs to be a null-check.  The point is not that it's hard to guard against those NPEs, or that the code was impossible to screw up under 1.4, just that it's kind of annoying that Java now gives you less help in guarding against them than it did before auto-unboxing was added; certain errors that the compiler would have caught in 1.4 are no longer caught in 1.5.

Doug McCreary replied on Wed, 2008/07/09 - 7:07pm in response to: Slava Imeshev

Mistake by omission is much more likely than mistake by addition.  That's the core of the problem.  You're more likely not to notice that you've made a null check error typing:

modifier.setEligible(!pattern.getDisplayEligibility());

 than

modifier.setEligible(!pattern.getDisplayEligibility().booleanValue());

I think that's the point.  In the second case you are forced to make a decision to call a function on the dereference, which might remind you to null check the dereference first.  In the first case, there's nothing whatsoever there to remind you that there is a subsequent function call on the dereference.   It's inviting mistakes.

 

 

Francisco Areas... replied on Thu, 2008/07/10 - 3:12pm in response to: Bob Morlaix

That's exactly what I thought. As a matter of fact, if when unboxing, it return 0 or false instead of the NPE, it would be a much more hard error to find.

 

[quote=BobMorlaix]What seems weird to me, is that the two methods of your example that are supposed to return Boolean values may return null... IMHO, methods that returns boxed values (i.e. Integer, Float, Boolean, ...) should never return null. What does it mean when a method that is supposed to return a Boolean value returns null? It doesn't mean "true", it doesn't mean "false"... Does it mean "maybe"?... [/quote]

Honey Monster replied on Sat, 2008/07/12 - 4:43am in response to: John Kelvie

[quote=jkelvie]It does add some complexity and the possibility for a type of error that did not exist before. I think it's worth it, but this is certainly a drawback. I think it's worth noting that the .NET implementation is almost identical, down to the exception throwing in the case of the value-type variable being null.[/quote]

.NET value types cannot be null. Period. They are value types and not reference types. They are not referenced via a pointer; rather they are like Javas primitive types. Only they are termed value types because the .NET developer can design new "primitive" types with stack-allocation and copy semantics, like e.g. big integers. 

While .NET features auto-boxing (casting a value type to object) it does not have auto-unboxing. I agree with the original poster that auto unboxing is a needless complexity.

In Java you use boxed primitives for two main (and largely unrelated) reasons: 1) To use primitive types with generics and 2) to allow for nullable primitive types.

In the first case you really don't want the nullable characteristic while that is exactly what you want the the second case. It is unfortunate that Javas design lumps these together.

In .NET you would simply use a primitive/value type for the first reason (use with generics), as .NET fully supports type parameters of any kind. For the second reason (explicitly nullable types) you would use the ? type suffix or the equivalent System.Nullable<T> generic. Nullable types also requires explicit unboxing by dereferening HasValue and Value respectively - or by an explicit cast.

So no, the .NET implementation is far from identical. .NET seperates the reasons for using boxed types into two seperate constructs. It does not use auto unboxing under any circumstances, and thus does not exhibit the problem described above.

Francisco Areas... replied on Sat, 2008/07/12 - 11:06am in response to: Honey Monster

.NET has the benefit of being made after Java, in both the senses of being released later and of mimicking both syntax and features. It would be very odd indeed, if it had the same problem Java have. One of the main advantages of newer languages (or platforms) is exactly to fix errors from the past.

That beeing said, in my personal opinion, auto-boxing an unboxing was made with the intention of using primitves in places where it was expected an Object. The most clear and used example is being able to use an int as a key  in a map. I believe that's the main use for the primitive type wrappers available in Java. The code below shows an example of this:


public class Test {

public static void main(String[] args) {
ArrayList<Integer> a = new ArrayList<Integer>();
a.add(5);
System.out.println(get(a, 0));
}

public static int get(ArrayList<Integer> a, int index) {
return a.get(index);
}

}

 

Here autoboxing is used to store the numbers inside the list, and auto-unboxing to retrieve them, but you should not work directly with the wrapper objects when wroking with the numbers, even because most of them are immutable.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.