Type Conversions in an Assignment Context
If the target and the source have the same type in an assignment, then obviously the source and the target are assignment compatible and the source value need not be converted. Otherwise, if a widening primitive conversion is permissible, then the widening conversion is applied implicitly; that is, the source type is converted to the target type in an assignment context.
// Widening Primitive Conversions
int smallOne = 1234; // No widening necessary.
long bigOne = 2020; // Widening: int to long.
double largeOne = bigOne; // Widening: long to double.
double hugeOne = (double) bigOne; // Cast redundant but allowed.
A widening primitive conversion can result in loss of precision. In the next example, the precision of the least significant bits of the long value may be lost when it is converted to a float value:
long bigInteger = 98765432112345678L;
float fpNum = bigInteger; // Widening but loss of precision: 9.8765436E16
Additionally, implicit narrowing primitive conversions on assignment can occur in cases where all of the following conditions are fulfilled:
- The source is a constant expression of type byte, short, char, or int.
- The target type is of type byte, short, or char.
- The value of the source is determined to be in the range of the target type at compile time.
A constant expression is an expression that denotes either a primitive or a String literal; it is composed of operands that can be only literals or constant variables, and operators that can be evaluated only at compile time (e.g., arithmetic and numerical comparison operators, but not increment/decrement operators and method calls). A constant variable is a final variable of either a primitive type or the String type that is initialized with a constant expression.
int result = 100; // Not a constant variable. Not declared final.
final char finalGrade = ‘A’; // Constant variable. ’A’
System.out.printf(“%d%n%s%n%d%n%.2f%n%b%n%d%n%d%n”,
2022, // Constant expression. 2022
“Trust ” + “me!”, // Constant expression. “Trust me”
2 + 3 * 4, // Constant expression. 14
Math.PI * Math.PI * 10.0, // Constant expression. 98.70
finalGrade == ‘A’, // Constant expression. true
Math.min(2020, 2021), // Not constant expression. Method call.
++result // Not constant expression. Increment operator.
);
Here are some examples that illustrate how the conditions mentioned previously affect narrowing primitive conversions:
// Conditions fulfilled for implicit narrowing primitive conversions.
short s1 = 10; // int value in range.
short s2 = ‘a’; // char value in range.
char c1 = 32; // int value in range.
char c2 = (byte)35; // byte value in range. (int value in range, without cast.)
byte b1 = 40; // int value in range.
byte b2 = (short)40; // short value in range. (int value in range, without cast.)
final int i1 = 20; // Constant variable
byte b3 = i1; // final value of i1 in range.
All other narrowing primitive conversions will produce a compile-time error on assignment and will explicitly require a cast. Here are some examples:
// Conditions not fulfilled for implicit narrowing primitive conversions.
// A cast is required.
int i2 = -20; // i2 is not a constant variable. i2 is not final.
final int i3 = i2; // i3 is not a constant variable, since i2 is not.
final int i4 = 200; // i4 is a constant variable.
final int i5; // i5 is not a constant variable.
short s3 = (short) i2; // Not constant expression.
char c3 = (char) i3; // Final value of i3 not determinable at compile time.
char c4 = (char) i2; // Not constant expression.
byte b4 = (byte) 128; // int value not in range.
byte b5 = (byte) i4; // Value of constant variable i4 is not in range.
i5 = 100; // Initialized at runtime.
short s4 = (short) i5; // Final value of i5 not determinable at compile time.
Floating-point values are truncated when cast to integral values.
// The value is truncated to fit the size of the target type.
float huge = (float) 1.7976931348623157d; // double to float.
long giant = (long) 4415961481999.03D; // (1) double to long.
int big = (int) giant; // (2) long to int.
short small = (short) big; // (3) int to short.
byte tiny = (byte) small; // (4) short to byte.
char symbol = (char) 112.5F; // (5) float to char.
Table 2.19 shows how the values are truncated for assignments from (1) to (5).
Table 2.19 Examples of Truncated Values
The discussion of numeric assignment conversions also applies to numeric parameter values at method invocation (§3.10, p. 129), except for the narrowing conversions, which always require a cast.
The following examples illustrate boxing and unboxing in an assignment context:
Click here to view code image Boolean boolRef = true; // Boxing.
Byte bRef = 2; // Constant in range: narrowing, then boxing.
// Byte bRef2 = 257; // Constant not in range. Compile-time error!
short s = 10; // Narrowing from int to short.
// Integer iRef1 = s; // short not assignable to Integer.
Integer iRef3 = (int) s; // Explicit widening with cast to int and boxing
boolean bv1 = boolRef; // Unboxing.
byte b1 = bRef; // Unboxing.
int iVal = bRef; // Unboxing and widening.
Integer iRefVal = null; // Always allowed.
// int j = iRefVal; // NullPointerException at runtime.
if (iRef3 != null) iVal = iRef3; // Avoids exception at runtime.