NARROWING AND WIDENING
CONVERSIONS
Suppose you're doing some C or C++ programming, and you have a
sequence of statements like this:
long a;
short b;
a = 123456;
b = a;
What happens? This is probably a programmer error. Here a value
(123456) is assigned to a variable (b) that's probably
too small to represent it. (This assumes that a value of data type
short is 16 bits). C and C++ do not prohibit this operation; the
result is implementation-defined. An experiment with a couple of
C++ compilers shows a value of -7616 for b.
This usage is illegal in the Java(tm) programming language, and it
serves to illustrate the way in which the language treats
conversions. Specifically, when you convert a larger primitive type
(like long) to a smaller primitive type (like short), it requires
a cast to be valid. For example:
b = (short)a;
Converting a large primitive type to a smaller primitive type is
called "narrowing primitive conversion". This type of conversion
has the potential for some loss of information. That's because
all but the lowest bits of data are discarded in the conversion.
In this example, the lowest 16 bits are saved, because a short is
guaranteed to have exactly 16 bits. By dropping the higher bits of
data, you might lose information about the magnitude and precision
of the original value. The magnitude is the range of values that a
primitive type can represent. For example, if you convert a long to
a byte, you'll probably lose magnitude information, because a long
can represent a much wider range of values using 64 bits than a
byte can using 8 bits.
The idea of precision can be illustrated using float and double.
You can represent a value like "pi" with more significant digits
if you use a double instead of a float.
In addition to a loss of magnitude and precision, the sign of the
converted value can be different than the sign of the original
value.
Widening conversions are analogous to narrowing conversions. A
widening conversion never loses information about magnitude, but can
lose precision. For example, if you convert a long to a float, you
might sacrifice precision. A float has 32 bits instead of 64,
and uses some of those bits for an exponent. So a float cannot
represent all 64 bits of the long value. A float can represent the
magnitude of a long, but not necessarily the precision. No cast is
required for a widening conversion such as:
long a;
float f;
a = 1234567890;
f = a;
Narrowing and widening primitive conversions never result in a
run-time exception, even in cases where information is lost.
There's a special case known as an "assignment conversion" that
handles some conversion cases that would otherwise seem to be
illegal, such as:
byte x;
x = 59;
In this example, a variable of type byte is being assigned a value
of type int. This implies a narrowing conversion. And it's legal
if (a) the expression to be assigned (59) is of constant int type,
(b) it's being assigned to a variable (x) of type byte, short, or
char, and (c) it will fit into the variable without losing any
information. In other words, you can assign small integer constants
to byte, char, and short variables without worrying about using a
cast.
Narrowing and widening conversions also apply to reference types.
For example, if you have:
class A {}
class B extends A {}
...
A aref = new A();
B bref = new B();
then usage like:
aref = bref;
is a widening conversion, and usage such as:
bref = (B)aref;
is a narrowing conversion. Widening reference conversions never
throw a run-time exception, but narrowing reference conversions
can throw a ClassCastException for usage like this:
class A {}
class B extends A {}
class C extends A {}
...
B bref = new B();
A aref = bref;
C cref = (C)aref;
The variable "aref" references a B, not a C, and an attempt to
force the B into a C results in an exception.
|