tags:

views:

933

answers:

8

Mixing the use of primitive data types and their respective wrapper classes, in Java, can lead to a lot of bugs. The following example illustrates the issue:

int i = 4;
...
if (i == 10)
  doCrap();

Later on you figure that you want the variable i to be either defined or undefined, so you change the above instantiation to:

Integer i = null;

Now the equality check fails.

Is it good Java practise to always use the primitive wrapper classes? It obviously would get some bugs out of the way early, but what are the downsides to this? Does it impact performance or the application's memory footprint? Are there any sneaky gotchas?

+6  A: 

Firstly, switching from using a primitive to using an object just to get the ability to set it to null is probably a bad design decision. I often have arguments with my coworkers about whether or not null is a sentinel value, and my opinion is usually that it is not (and thus shouldn't be prohibited like sentinel values should be), but in this particular case you're going out of your way to use it as a sentinel value. Please don't. Create a boolean that indicates whether or not your integer is valid, or create a new type that wraps the boolean and integer together.

Usually, when using newer versions of Java, I find I don't need to explicitly create or cast to the object versions of primitives because of the auto-boxing support that was added some time in 1.5 (maybe 1.5 itself).

rmeador
+10  A: 

Using the boxed types does have both performance and memory issues.

When doing comparisons (eg (i == 10) ), java has to unbox the type before doing the comparison. Even using i.equals(TEN) uses a method call, which is costlier and (IMO) uglier than the == syntax.

Re memory, the object has to be stored on the heap (which also takes a hit on performance) as well as storing the value itself.

A sneaky gotcha? i.equals(j) when i is null.

I always use the primitives, except when it may be null, but always check for null before comparison in those cases.

Michael Deardeuff
It does it the other way I belive (ie: it boxes 10 into an Integer then compares the two Integer instances to see if they are the same instance using == )
tim_yates
No. It unboxes the Integer to a primitive int.
erickson
A *really* sneaky gotcha? i == j when i is null.
erickson
A: 

Thee java POD types are there for a reason. Besides the overhead, you can't do normal operations with objects. An Integer is an object, which need to be allocated and garbage collected. An int isn't.

David Nehme
An int may not be an object, but it still needs you to allocate memory for its storage, and that memory still needs to be released afterwards.
Tommy Herbert
@Tommy: No, it doesn't. It just uses a slot on the execution stack, which doesn't count as "allocation".
Chris Jester-Young
+1 to Chris and David, -1 to Tommy. An Integer instance has all the trappings of an object (consumes much more than the 4 bytes a primitive int does) and is tracked independently by the garbage collector. An int is just on the stack or in a field, and dies with its stack frame or owning instance.
erickson
+1  A: 

In your example, the if statement will be ok until you go over 127 (as Integer autoboxing will cache values up to 127 and return the same instance for each number up to this value)

So it is worse than you present it...

if( i == 10 )

will work as before, but

if( i == 128 )

will fail. It is for reasons like this that I always explicitly create objects when I need them, and tend to stick to primitive variables if at all possible

tim_yates
This is incorrect. Comparing a boxed type with a numeric primitive will cause the boxed type to be unboxed and the comparison will succeed as expected. You've confused the real problem which is that comparing two boxed types is wrong. I.e., given "Integer v1 = 128, v2 = 128;" (v1 == v2) is false.
erickson
Here is a good explanation of the Integer caching:http://www.owasp.org/index.php/Java_gotchas
Kamikaze Mercenary
+1  A: 

I'd suggest using primitives all the time unless you really have the concept of "null".

Yes, the VM does autoboxing and all that now, but it can lead to some really wierd cases where you'll get a null pointer exception at a line of code that you really don't expect, and you have to start doing null checks on every mathematical operation. You also can start getting some non-obvious behaviors if you start mixing types and getting wierd autoboxing behaviors.

For float/doubles you can treat NaN as null, but remember that NaN != NaN so you still need special checks like !Float.isNaN(x).

It would be really nice if there were collections that supported the primitive types instead of having to waste the time/overhead of boxing.

John Gardner
A: 

If that value can be empty, you may find that in your design you are in need of something else.

There are two possibilities--either the value is just data (the code won't act any differently if it's filled in or not), or it's actually indicating that you have two different types of object here (the code acts differently if there is a value than a null)

If it's just data for display/storage, you might consider using a real DTO--one that doesn't have it as a first-class member at all. Those will generally have a way to check to see if a value has been set or not.

If you check for the null at some point, you may want to be using a subclass because when there is one difference, there are usually more. At least you want a better way to indicate your difference than "if primitiveIntValue == null", that doesn't really mean anything.

Bill K
A: 

Don't switch to non-primitives just to get this facility. Use a boolean to indicate whether the value was set or not. If you don't like that solution and you know that your integers will be in some reasonable limit (or don't care about the occasional failure) use a specific value to indicate 'uninitialized', such as Integer.MIN_VALUE. But that's a much less safe solution than the boolean.

DJClayworth
A: 

When you got to that 'Later on' point, a little more work needed to be accomplished during the refactoring. Use primitives when possible. (Capital period) Then make POJOs if more functionality is needed. The primitive wrapper classes, in my opinion, are best used for data that needs to travel across the wire, meaning networked apps. Allowing nulls as acceptable values causes headaches as a system 'grows'. To much code wasted, or missed, guarding what should be simple comparisons.

Dennis S