3.4 Simple Assignment Operator '='

The assignment statement has the following syntax:


<variable> = <expression>

which can be read as "the destination, <variable>, gets the value of the source, <expression>". The previous value of the destination variable is overwritten by the assignment operator =.

The destination <variable> and the source <expression> must be type compatible. The destination variable must also have been declared. Since variables can store either primitive data values or object references, <expression> evaluates to either a primitive data value or an object reference.

Assigning Primitive Values

The following examples illustrate assignment of primitive values:

int j, k;
j = 10;           // j gets the value 10.
j = 5;            // j gets the value 5. Previous value is overwritten.
k = j;            // k gets the value 5.

The assignment operator has the lowest precedence, allowing the expression on the right-hand side to be evaluated before assignment.

int i;
i = 5;            // i gets the value 5.
i = i + 1;        // i gets the value 6. + has higher precedence than =.
i = 20 - i * 2;   // i gets the value 8: (20 - (i * 2))

Assigning References

Copying references by assignment creates aliases, which is discussed in Section 1.3 on page 5. The following example recapitulates that discussion:

Pizza pizza1 = new Pizza("Hot&Spicy");
Pizza pizza2 = new Pizza("Sweet&Sour");

pizza2 = pizza1;

Variable pizza1 is a reference to a pizza that is hot and spicy, and pizza2 is a reference to a pizza which is sweet and sour. Assigning pizza1 to pizza2 means that pizza2 now references the same pizza as pizza1, that is, the hot and spicy one. After assignment these variables are aliases, and either one can be used to manipulate the hot and spicy Pizza object.

Assigning a reference does not create a copy of the source object denoted by the reference variable on the right-hand side. Reference assignment also does not copy the state of the source object to any object denoted by the reference variable on the left-hand side. It merely assigns the reference value to the variable on the right-hand side to the variable on the left-hand side, so that they denote the same object.

A more detailed discussion of reference assignment can be found in Section 6.6.

Multiple Assignments

The assignment statement is an expression statement, which means that application of the binary assignment operator returns the value of the expression on the right-hand side.

int j, k;
j = 10;           // j gets the value 10 which is returned
k = j;            // k gets the value of j, which is 10, and this value is returned

The last two assignments can be written as multiple assignments, illustrating the right associativity of the assignment operator.

k = j = 10;       // (k = (j = 10))

Multiple assignments are equally valid with references.

Pizza pizzaOne, pizzaTwo;
pizzaOne = pizzaTwo = new Pizza("Supreme"); // Aliases.

The following example shows the effect of operand evaluation order:

int[] a = {10, 20, 30, 40, 50}; // an array of int
int index = 4;
a[index] = index = 2;

What is the value of index, and which array element a[index] is assigned a value in the multiple assignment statement? The evaluation proceeds as follows:

a[index] = index = 2;
a[4]     = index = 2;
a[4]     = (index = 2);      // index gets the value 2. = is right associative.
a[4]     =      2;           // The value of a[4] is changed from 50 to 2.

Numeric Type Conversions on Assignment

If the destination and source have the same type in an assignment, then, obviously, the source and the destination are type 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 promoted to the destination type in an assignment context.

// Implicit Widening Primitive Conversions
int    smallOne = 1234;
long   bigOne   = 2000;               // Implicit widening: int to long.
double largeOne = bigOne;             // Implicit widening: long to double.
double hugeOne  = (double) bigOne;    // Cast redundant but allowed.

Integer values widened to floating-point values can result in loss of precision. Precision relates to the number of significant bits in the value, and must not be confused with magnitude, which relates how big a value can be represented. In the next example, the precision of the least significant bits of the long value may be lost when converting to a float value.

long bigInteger = 98765432112345678L;
float realNo = 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 either byte, short, char, or int type

  • the destination type is either byte, short, or char type

  • the value of the source is determined to be in the range of the destination type at compile time

// Above 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;
byte b3 = i1;           // final value of i1 in range.

// Above conditions not fulfilled for implicit narrowing primitive conversions.
// Explicit cast required.
int i2 = -20;
final int i3 = i2;
final int i4 = 200;
short s3 = (short) i2;    // Not constant expression.
char c3 = (char) i3;      // final value of i3 not determinable.
char c4 = (char) i2;      // Not constant expression.
byte b4 = (byte) 128;     // int value not in range.
byte b5 = (byte) i4;      // final value of i4 not in range.

All other narrowing primitive conversions will produce a compile-time error on assignment, and will explicitly require a cast.

Floating-point values are truncated when converted to integral values.

// Explicit narrowing primitive conversions requiring cast.
// The value is truncated to fit the size of the destination 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  minute = (byte) small;                // (4) short to byte.
char  symbol = (char) 112.5F;               // (5) float to char.

Table 3.2 shows how the values are truncated for lines marked (1) to (5) in the previous code.

Table 3.2. Examples of Truncated Values

Binary

Decimal

 

0000000000000000000001000000010000101011110100001100001100001111

4415961481999

(1)

00101011110100001100001100001111

735101711

(2)

1100001100001111

-15601

(3)

00001111

15

(4)

0000000001110000

'p'

(5)

The discussion on numeric assignment conversions also applies to numeric parameter values at method invocation (see Section 3.18, p. 88), except for the narrowing conversions, which always require a cast.