5.9 Reference Value Assignment Conversions
In the context of assignments, the following conversions are permitted (Table 2.17, p. 47):
- Widening primitive and reference conversions (long ← int, Object ← String)
- Boxing conversion of primitive values, followed by optional widening reference conversion (Integer ← int, Number ← Integer ← int)
- Unboxing conversion of a primitive value wrapper object, followed by optional widening primitive conversion (long ← int ← Integer)
In addition, for assignment conversions only, the following conversion is also possible:
- Narrowing conversion for constant expressions of non-long integer types, with optional boxing (Byte ← byte ← int)
Note that these rules imply that a widening conversion cannot be followed by any boxing conversion, but the converse is permitted.
Widening reference conversions typically occur during assignment up the type hierarchy, with implicit conversion of the source reference value to that of the destination reference type:
Object obj = “Up the tree”; // Widening reference conversion: Object <– String
String str1 = obj; // Not OK. Narrowing reference conversion requires a cast.
String str2 = Integer.valueOf(10); // Illegal. No relation between
// String and Integer.
The source value can be a primitive value, in which case the value is boxed in a wrapper object corresponding to the primitive type. If the destination reference type is a supertype of the wrapper type, a widening reference conversion can occur:
Integer iRef = 10; // Only boxing
Number num = 10L; // Boxing, followed by widening: Number <— Long <— long
Object obj = 100; // Boxing, followed by widening: Object <— Integer <— int
More examples of boxing during assignment can be found in §2.3, p. 45.
Example 5.18 Assigning and Passing Reference Values
// See
Example 5.10
,
p. 241
, for type declarations.
interface IStack
interface ISafeStack extends IStack
class Stack implements IStack
class SafeStack extends Stack implements ISafeStack
public class ReferenceConversion {
public static void main(String[] args) {
// Reference declarations:
Object objRef;
Stack stackRef;
SafeStack safeStackRef;
IStack iStackRef;
ISafeStack iSafeStackRef;
// SourceType is a class type:
safeStackRef = new SafeStack(10);
objRef = safeStackRef; // (1) Always possible
stackRef = safeStackRef; // (2) Subclass to superclass assignment
iStackRef = stackRef; // (3) Stack implements IStack
iSafeStackRef = safeStackRef; // (4) SafeStack implements ISafeStack
// SourceType is an interface type:
objRef = iStackRef; // (5) Always possible
iStackRef = iSafeStackRef; // (6) Sub- to superinterface assignment
// SourceType is an array type:
Object[] objArray = new Object[3];
Stack[] arrayOfStack = new Stack[3];
SafeStack[] arrayOfSafeStack = new SafeStack[5];
ISafeStack[] arrayOfISafeStack = new ISafeStack[5];
int[] intArray = new int[10];
// Reference value assignments:
objRef = objArray; // (7) Always possible
objRef = arrayOfStack; // (8) Always possible
objArray = arrayOfStack; // (9) Always possible
objArray = arrayOfISafeStack; // (10) Always possible
objRef = intArray; // (11) Always possible
// objArray = intArray; // (12) Compile-time error:
// int[] not subtype of Object[]
arrayOfStack = arrayOfSafeStack; // (13) Subclass array to superclass array
arrayOfISafeStack = arrayOfSafeStack; // (14) SafeStack implements
// ISafeStack
// Method invocation conversions:
System.out.println(“First call:”);
sendParams(stackRef, safeStackRef, iStackRef,
arrayOfSafeStack, arrayOfISafeStack); // (15)
// Call Signature: sendParams(Stack, SafeStack, IStack,
// SafeStack[], ISafeStack[]);
System.out.println(“Second call:”);
sendParams(arrayOfISafeStack, stackRef, iSafeStackRef,
arrayOfStack, arrayOfSafeStack); // (16)
// Call Signature: sendParams(ISafeStack[], Stack, ISafeStack,
// Stack[], SafeStack[]);
}
public static void sendParams(Object objRefParam, Stack stackRefParam,
IStack iStackRefParam, Stack[] arrayOfStackParam,
IStack[] arrayOfIStackParam) { // (17)
// Signature: sendParams(Object, Stack, IStack, Stack[], IStack[])
// Print class name of object denoted by the reference at runtime.
System.out.println(objRefParam.getClass());
System.out.println(stackRefParam.getClass());
System.out.println(iStackRefParam.getClass());
System.out.println(arrayOfStackParam.getClass());
System.out.println(arrayOfIStackParam.getClass());
}
}
Output from the program:
First call:
class SafeStack
class SafeStack
class SafeStack
class [LSafeStack;
class [LSafeStack;
Second call:
class [LSafeStack;
class SafeStack
class SafeStack
class [LSafeStack;
class [LSafeStack;
The rules for reference value assignment are stated in this section, based on the following code:
SourceType srcRef;
// srcRef is appropriately initialized.
DestinationType destRef = srcRef;
If an assignment is legal, the reference value of srcRef is said to be assignable (or assignment compatible) to the reference of DestinationType. The rules are illustrated by concrete cases from Example 5.18. Note that the code in Example 5.18 uses reference types from Example 5.10, p. 241.
- If the SourceType is a class type, the reference value in srcRef may be assigned to the destRef reference, provided the DestinationType is one of the following:
DestinationType is a superclass of the subclass SourceType.
DestinationType is an interface type that is implemented by the class SourceType.
objRef = safeStackRef; // (1) Always possible
stackRef = safeStackRef; // (2) Subclass to superclass assignment
iStackRef = stackRef; // (3) Stack implements IStack
iSafeStackRef = safeStackRef; // (4) SafeStack implements ISafeStack
- If the SourceType is an interface type, the reference value in srcRef may be assigned to the destRef reference, provided the DestinationType is one of the following:
DestinationType is the Object class.
DestinationType is a superinterface of the subinterface SourceType.
objRef = iStackRef; // (5) Always possible
iStackRef = iSafeStackRef; // (6) Subinterface to superinterface assignment
- If the SourceType is an array type, the reference value in srcRef may be assigned to the destRef reference, provided the DestinationType is one of the following:
DestinationType is the Object class.
DestinationType is an array type, where the element type of the SourceType is assignable to the element type of the DestinationType.
objRef = objArray; // (7) Always possible
objRef = arrayOfStack; // (8) Always possible
objArray = arrayOfStack; // (9) Always possible
objArray = arrayOfISafeStack; // (10) Always possible
objRef = intArray; // (11) Always possible
// objArray = intArray; // (12) Compile-time error:
// int[] not subtype of Object[]
arrayOfStack = arrayOfSafeStack; // (13) Subclass array to superclass array
arrayOfISafeStack = arrayOfSafeStack; // (14) SafeStack implements
// ISafeStack
The rules for assignment are enforced at compile time, guaranteeing that no type conversion error will occur during assignment at runtime. Such conversions are type-safe. The reason the rules can be enforced at compile time is that they concern the declared type of the reference (which is always known at compile time) rather than the actual type of the object being referenced (which is known at runtime).
Leave a Reply