In the previous section on wrapper
classess for primitive type values, we discussed how to create an
instance of a wrapper from a primitive and conversely, how to obtain
the primitive value held by the wrapper. This involves a a certain
amount of clumsy code.
For example, creating a Float
object from a float primitive is straightforward:
float
primitive_float = 3.0f;
Float
wrapper_float = new Float (primitive_float);
Going the other direction, however, requires explicitly calling
the floatValue()
method on the Float
object:
float
primitive_float = wrapper_float.floatValue ();
If you are dealing with a lot of instances of wrappers and conversions,
you will thus need to deal with a lot of method invocations.
In J2SE 5.0, however, the code to create the wrapper object allows
for this much simpler form:
Float
wrapper_float = primitive_float;
Here, the "wrapping" is done automatically! There is no need to
explicitly call the Float
constructor. This "wrapping" is called "autoboxing" in the sense
that the primitive value is automatically "boxed up" into the wrapper
object. Autoboxing is available for all the primitive/wrapper types.
Going the other way, from object type to primitive, is just as
simple:
Integer
wrapper_integer = 5; // primitive 5 autoboxed into an Integer
int primitive_int = wrapper_integer; // automatic unboxing
Integer into int
These shortcuts simplify coding and reduce errors in J2SE 5.0.
For example, they can even be used in loop control and incrementing
and decrementing operations. For a contrived example, consider adding
all the integers from 1 to 100 as shown in the following program:
BoxApplet.java
(Output goes to browser's Java
console.) |
public
class BoxApplet extends java.applet.Applet
{
public void init () {
int MAX = 100;
// a primitive int type
Integer counter = 1; // an Integer
type
Integer sum = 0;
// ditto
while (true) {
sum += counter;
if (counter == MAX)
break;
counter++;
}
System.out.println ("counter is
now " + counter);
System.out.println ("sum is now
" + sum);
System.out.println ("MAX*(MAX+1)/2
is " + MAX*(MAX+1)/2);
} // init
// Paint message in Applet window.
public void paint (java.awt.Graphics g) {
g.drawString ("BoxApplet", 20,
20);
}
} // class BoxApplet
|
|
There is a lot of hidden autoboxing and unboxing going on in this
simple-looking code. First, the Integer
types counter
and sum
are autoboxed from the primitive values 1 and 0. Then, in the loop,
they are unboxed to primitive values so the +=
operation can be applied and then reboxed to their "native" Integer
types.
To do the ==
comparison counter
is unboxed so it can be compared with the int
type MAX.
If the break does not apply, then counter
is unboxed, operated on with ++,
and then reboxed.
Autoboxing and unboxing work in a for loop as well:
Integer
sum = 0;
for (Integer counter=1; counter < MAX; counter++)
{
sum += counter;
}
Note that both of these loops are likely to perform very slowly
with all these autoboxing and unboxing operations. An optimizing
compiler might be able to avoid some of the autoboxing and unboxing
operations, but in general you should do long looping operations
with primitive types unless there is a very good reason to use a
wrapper type.
Autoboxing and unboxing also work with Boolean and boolean types.
For example,
boolean
one = true; // nothing new here
Boolean two = true; // autoboxing of primitive 'true'
to Boolean type
if (one && two) // auto unboxing do_something ();
Before 5.0, the if,
while,
and do-while
statements (see Chapter 2)
all expected boolean expressions. Through the use of unboxing, those
flow control statements now also accept expressions that evaluate
to Boolean
types.
Similarly, the old switch statement expects a byte, short, int,
or char type in Java 1.4 and below. With the addition of autoboxing
in 5.0, switch now also accepts Byte,
Short,
Integer,
and Character
types.
Where autoboxing and unboxing become particularly useful is with
the insertion and retrieval of primitive values into and out of
object containers like Vector
and ArrayList
(see Chapter 10). Since the
container classes only accept objects, not primitives, prior to
J2SE 5.0 it was necessary to convert primitives to wrapper object
types for insertion and to convert wrapper objects back to primitives
after retrieval. Autoboxing and unboxing now make these operations
almost transparent.
With the additional new generics feature in 5.0, using primitives
with container objects is even simpler. We postpone that discussion
until we discuss the new generics feature in Chapter 10.
Autoboxing and Overloading
Autoboxing and unboxing can make method overloading interesting.
Consider the two overloaded methods shown here
long
method1 (long l) { return l+1; }
long
method1 (Integer i) { return i+2; }
If you call method1()
with a primitive long
parameter, then the first method1()
is used. If you call method1()
with an Integer
object parameter, then the second method1()
is used. There is nothing new there. But what happens if you call
method1()
with an int
parameter? In J2SE 1.4 and below, the int
is promoted to a long
and the first method1()
is used. With autoboxing, it is conceivable that the int
could be boxed into an Integer
type and the second method1()
used. That might even be what you want to happen - it might make
more sense to convert an int
to an Integer
than to promote it to a long. While arguably reasonable, that is
not what happens. The general rule is, for compatibility reasons,
the same behavior that applied in pre-5.0 versions must continue
to hold. The reason is that existing code cannot suddenly start
behaving differently when compiled and run under 5.0.
We would suggest that an even better rule is to realize that using
overloads like this is confusing and potentially asking for trouble.
There is little reason to write such obfuscated code.
References & Web Resources
Latest update: Nov. 17, 2004
|