Reference Value Assignment Conversions – Object-Oriented Programming

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:

Click here to view code image

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:

Click here to view code image

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

Click here to view code image

// 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

Click here to view code image

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:

Click here to view code image

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:

Click here to view code image

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:

Image DestinationType is a superclass of the subclass SourceType.

Image DestinationType is an interface type that is implemented by the class SourceType.

Click here to view code image

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:

Image DestinationType is the Object class.

Image DestinationType is a superinterface of the subinterface SourceType.

Click here to view code image

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:

Image DestinationType is the Object class.

Image DestinationType is an array type, where the element type of the SourceType is assignable to the element type of the DestinationType.

Click here to view code image

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).


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *