5 Operators and Expressions

5.1 Conversions

In this section we first discuss the different kinds of type conversions that can be applied to values, and in the next section we discuss the contexts in which these conversions are permitted. Some type conversions must be explicitly stated in the program, while others are performed implicitly. Some type conversions can be checked at compile time to guarantee their validity at runtime, while others will require an extra check at runtime.

Widening and Narrowing Primitive Conversions

For the primitive data types, the value of a narrower data type can be converted to a value of a wider data type. This is called a widening primitive conversion. Widening conversions from one primitive type to the next wider primitive type are summarized in Figure 5.1. The conversions shown are transitive. For example, an int can be directly converted to a double without first having to convert it to a long and a float.

Figure 5.1 Widening Primitive Conversions

Widening Primitive Conversions

Note that the target type of a widening primitive conversion has a wider range of values than the source type, e.g., the range of the long type subsumes the range of the int type. In widening conversions between integral types, the source value remains intact, with no loss of magnitude information. However, a widening conversion from an int or a long value to a float value, or from a long value to a double value, may result in a loss of precision. The floating-point value in the target type is then a correctly rounded approximation of the integer value. Note that 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.

Converting from a wider primitive type to a narrower primitive type is called a narrowing primitive conversion, which can result in loss of magnitude information, and possibly in precision as well. Any conversion which is not a widening primitive conversion according to Figure 5.1 is a narrowing primitive conversion. The target type of a narrowing primitive conversion has a narrower range of values than the source type, for example, the range of the int type does not include all the values in the range of the long type.

Note that all conversions between char and the two integer types byte and short are considered narrowing primitive conversions: the reason being that the conversions between the unsigned type char and the signed types byte or short can result in loss of information. These narrowing conversions are done in two steps, first converting the source value to the int type, and then converting the int value to the target type.

Widening primitive conversions are usually done implicitly, whereas narrowing primitive conversions usually require a cast (Section 5.2, p. 164). It is not illegal to use a cast for a widening conversion. However, the compiler will flag any conversion that require a cast if none has been specified. Regardless of any loss of magnitude or precision, widening and narrowing primitive conversions never result in a runtime exception.

Ample examples of widening and narrowing primitive conversions can be found in this chapter and also in Section 3.7, p. 81.

Widening and Narrowing Reference Conversions

The subtype-supertype relationship between reference types determines which conversions are permissible between them. Conversions up the type hierarchy are called widening reference conversions (also called upcasting), i.e., such a conversion converts from a subtype to a supertype.

      Object obj = "upcast me"; // Widening: Object <- String

Conversions down the type hierarchy represent narrowing reference conversions (also called downcasting).

      String str = (String) obj; // Narrowing requires cast: String <- Object

A subtype is a narrower type than its supertype in the sense that it has no relationship with other subtypes of its supertype, i.e., a supertype can be the supertype of types that a subtype is not supertype of. For example, given that A is the supertype of immediate subtypes B, C, and D, each subtype is narrower than the supertype A, as any one of the subtypes cannot represent the other two subtypes. Contexts under which reference conversions can occur are discussed in Section 7.8, p. 319.

Widening reference conversions are usually done implicitly, whereas narrowing reference conversions usually require a cast (Section 5.2, p. 164). The compiler will reject casts that are not legal or issue an unchecked warning under certain circumstances if type safety cannot be guaranteed (Section 14.2, p. 670).

Widening reference conversions do not require any runtime checks and never result in an exception during execution. This is not the case for narrowing reference conversions, which require a runtime check and can throw a ClassCastException if the conversion is not legal.

Boxing and Unboxing Conversions

For an overview of the primitive types and their wrapper types, see Table 2.13, p. 30. For an overview of the methods provided by the wrapper types, see Section 10.3, p. 428.

A boxing conversion converts the value of a primitive type to a corresponding value of its wrapper type. If p is a value of a primitiveType, boxing conversion converts p into a reference r of corresponding WrapperType, such that r.primitiveTypeValue() == p. In the code below, the int value 10 results in an object of the type Integer implicitly being created that contains the int value 10. We say that the int value 10 has been boxed in an object of the wrapper type Integer.

      Integer iRef = 10;                           // Boxing: Integer <- int
      System.out.println(iRef.intValue() == 10);   // true

An unboxing conversion converts the value of a wrapper type to a value of its corresponding primitive type. If r is a reference of a WrapperType, unboxing conversion converts the reference r into r.primitiveTypeValue(), where primitiveType is the primitive type corresponding to the WrapperType. In the code below, the value in the Integer object referenced by iRef is implicitly converted to the int type. We say that the wrapper object has been unboxed to its corresponding primitive type.

      int i = iRef;                                // Unboxing: int <- Integer
      System.out.println(iRef.intValue() == i);    // true

Note that both boxing and unboxing are done implicitly in the right context. Boxing allows primitive values to be used where an object of their wrapper type is expected, and unboxing allows the converse. Unboxing makes it possible to use a Boolean wrapper object in a boolean expression and as an integral wrapper object in an arithmetic expression. Unboxing a wrapper reference that has the null value results in a NullPointerException. Ample examples of boxing and unboxing can be found in this chapter and in Section 7.8, p. 319.

Other Conversions

We briefly mention some other conversions and where they are covered in this book.

Identity conversions are always permitted, as they allow conversions from a type to that same type. An identity conversion is always permitted.

int i = (int) 10;                 // int < int
String str = (String) "Hi";       // String < String

String conversions allow a value of any other type to be converted to a String type in the context of the string concatenation operator + (Section 5.7, p. 185).

Unchecked conversions are permitted to facilitate operability between legacy and generic code (Section 14.2, p. 670).

Capture conversions aid in increasing the usefulness of wildcards in generic code (Section 14.9, p. 703).

5.2 Type Conversion Contexts

Selected conversion contexts and the conversions that are applicable in these contexts are summarized in Table 5.1. The conversions shown in each context occur implicitly, without the program having to take any special action. For other conversion contexts, see the sections mentioned in the subsection Other Conversions, p. 162.

Table 5.1 Selected Conversion Contexts and Conversion Categories

Selected Conversion Contexts and Conversion Categories

Assignment Context

Assignment conversions that can occur in an assignment context are shown in the second column of Table 5.1. An assignment conversion converts the type of an expression to the type of a target variable.

An expression (or its value) is assignable to the target variable, if the type of the expression can be converted to the type of the target variable by an assignment conversion. Equivalently, the type of the expression is assignment compatible with the type of the target variable.

For assignment conversion involving primitive data types, see Section 5.5, p. 169. Note the special case where a narrowing conversion occurs when assigning a non-long integer constant expression:

      byte b = 10;  // Narrowing conversion: byte <- int

For assignment conversions involving reference types, see Section 7.8, p. 319.

Method Invocation Context

Method invocation conversions that can occur in a method invocation context are shown in the third column of Table 5.1. Note that method invocation and assignment conversions differ in one respect: method invocation conversions do not include the implicit narrowing conversion performed for integer constant expressions.

A method invocation conversion involves converting each argument value in a method or constructor call to the type of the corresponding formal parameter in the method or constructor declaration.

Method invocation conversions involving parameters of primitive data types are discussed in Section 3.7, p. 82, and those involving reference types are discussed in Section 7.8, p. 319.

Casting Context of the Unary Type Cast Operator: (type)

Java, being a strongly typed language, checks for type compatibility (i.e., checks if a type can substitute for another type in a given context) at compile time. However, some checks are only possible at runtime (for example, which type of object a reference actually denotes during execution). In cases where an operator would have incompatible operands (e.g., assigning a double to an int), Java demands that a type cast be used to explicitly indicate the type conversion. The type cast construct has the following syntax:

      (<type>)<expression>

The cast operator (<type>) is applied to the value of the <expression>. At runtime, a cast results in a new value of <type>, which best represents the value of the <expression> in the old type. We use the term casting to mean applying the cast operator for explicit type conversion.

However, in the context of casting, implicit casting conversions can take place. These casting conversions are shown in the fourth column of Table 5.1. Casting conversions include more conversion categories than the assignment or the method invocation conversions. In the code below, the comments indicate the category of the conversion that takes place because of the cast operator on the right-hand side of each assignment—although some casts are not necessary for the sake of the assignment.

      long l = (long) 10;   // Widening primitive conversion: long <- int
      int i = (int) l;      // Narrowing primitive conversion: int <- long
      Object obj = (Object) "Upcast me"; // Widening ref conversion: Object <- String
      String str = (String) obj;         // Narrowing ref conversion: String <- Object
      Integer iRef = (Integer) i;        // Boxing: Integer <- int
      i = (int) iRef;                    // Unboxing: int <- Integer

A casting conversion is applied to the value of the operand <expression> of a cast operator. Casting can be applied to primitive values as well as references. Casting between primitive data types and reference types is not permitted, except where boxing and unboxing is applicable. Boolean values cannot be cast to other data values, and vice versa. The reference literal null can be cast to any reference type.

Examples of casting between primitive data types are provided in this chapter. Casting reference values is discussed in Section 7.11, p. 327. Implications that generics have on casting are discussed in Section 14.13, p. 724.

Numeric Promotion Context

Numeric operators only allow operands of certain types. Numeric promotion results in conversions being applied to the operands to convert them to permissible types. Numeric promotion conversions that can occur in a numeric promotion context are shown in the fifth column of Table 5.1. Permissible conversion categories are: widening primitive conversions and unboxing conversions. A distinction is made between unary and binary numeric promotion.

Unary Numeric Promotion

Unary numeric promotion proceeds as follows:

• If the single operand is of type Byte, Short, Character, or Integer, it is unboxed. If the resulting value is narrower than int, it is promoted to a value of type int by a widening conversion.

• Otherwise, if the single operand is of type Long, Float, or Double, it is unboxed.

• Otherwise, if the single operand is of a type narrower than int, its value is promoted to a value of type int by a widening conversion.

• Otherwise, the operand remains unchanged.

In other words, unary numeric promotion results in an operand value that is either int or wider.

Unary numeric promotion is applied in the following expressions:

• operand of the unary arithmetic operators + and - (see Section 5.6, p. 174)

• array creation expression; e.g., new int[20], where the dimension expression (in this case 20) must evaluate to an int value (see Section 3.6, p. 70)

• indexing array elements; e.g., objArray['a'], where the index expression (in this case 'a') must evaluate to an int value (see Section 3.6, p. 72)

Binary Numeric Promotion

Binary numeric promotion implicitly applies appropriate widening primitive conversions so that a pair of operands have the widest numeric type of the two, which is always at least int. Given T to be the widest numeric type of the two operands after any unboxing conversions have been performed, the operands are promoted as follows during binary numeric promotion:

If T is wider than int, both operands are converted to T; otherwise, both operands are converted to int.

This means that the resulting type of the operands is at least int.

Binary numeric promotion is applied in the following expressions:

• operands of the arithmetic operators *, /, %, +, and - (see Section 5.6, p. 174)

• operands of the relational operators <, <=, >, and >= (see Section 5.10, p. 190)

• operands of the numerical equality operators == and != (see Section 5.11, p. 191)

• operands of the conditional operator ? :, under certain circumstances (see Section 5.14, p. 201)

5.3 Precedence and Associativity Rules for Operators

Precedence and associativity rules are necessary for deterministic evaluation of expressions. The operators are summarized in Table 5.2. The majority of them are discussed in subsequent sections in this chapter.

Table 5.2 Operator Summary

Operator Summary

The following remarks apply to Table 5.2:

• The operators are shown with decreasing precedence from the top of the table.

• Operators within the same row have the same precedence.

• Parentheses, ( ), can be used to override precedence and associativity.

• The unary operators, which require one operand, include the following: the postfix increment (++) and decrement (--) operators from the first row, all the prefix operators (+, -, ++, --, ~, !) in the second row, and the prefix operators (object creation operator new, cast operator (type)) in the third row.

• The conditional operator (? :) is ternary, that is, requires three operands.

All operators not listed above as unary or ternary, are binary, that is, require two operands.

• All binary operators, except for the relational and assignment operators, associate from left to right. The relational operators are nonassociative.

• Except for unary postfix increment and decrement operators, all unary operators, all assignment operators, and the ternary conditional operator associate from right to left.

Precedence rules are used to determine which operator should be applied first if there are two operators with different precedence, and these follow each other in the expression. In such a case, the operator with the highest precedence is applied first.

2 + 3 * 4 is evaluated as 2 + (3 * 4) (with the result 14) since * has higher precedence than +.

Associativity rules are used to determine which operator should be applied first if there are two operators with the same precedence, and these follow each other in the expression.

Left associativity implies grouping from left to right:

1 + 2 - 3 is interpreted as ((1 + 2) - 3), since the binary operators + and - both have same precedence and left associativity.

Right associativity implies grouping from right to left:

-- 4 is interpreted as (- (- 4)) (with the result 4), since the unary operator - has right associativity.

The precedence and associativity rules together determine the evaluation order of the operators.

5.4 Evaluation Order of Operands

In order to understand the result returned by an operator, it is important to understand the evaluation order of its operands. In general, the operands of operators are evaluated from left to right.

The evaluation order also respects any parentheses, and the precedence and associativity rules of operators.

Examples illustrating how the operand evaluation order influences the result returned by an operator, can be found in Sections 5.5 and 5.8.

Left-Hand Operand Evaluation First

The left-hand operand of a binary operator is fully evaluated before the right-hand operand is evaluated.

The evaluation of the left-hand operand can have side effects that can influence the value of the right-hand operand. For example, in the following code:

int b = 10;
System.out.println((b=3) + b);

the value printed will be 6 and not 13. The evaluation proceeds as follows:

(b=3)    + b
    3           + b       b is assigned the value 3
    3           + 3
    6

If evaluation of the left-hand operand of a binary operator raises an exception (see Section 6.5, p. 235), we cannot rely on the presumption that the right-hand operand has been evaluated.

Operand Evaluation before Operation Execution

Java guarantees that all operands of an operator are fully evaluated before the actual operation is performed. This rule does not apply to the short-circuit conditional operators &&, ||, and ?:.

This rule also applies to operators that throw an exception (the integer division operator / and the integer remainder operate %). The operation is only performed if the operands evaluate normally. Any side-effects of the right-hand operand will have been effectuated before the operator throws an exception.

Left to Right Evaluation of Argument Lists

In a method or constructor invocation, each argument expression in the argument list is fully evaluated before any argument expression to its right.

If evaluation of an argument expression does not complete normally, we cannot presume that any argument expression to its right has been evaluated.

5.5 The Simple Assignment Operator =

The assignment statement has the following syntax:

<variable> = <expression>

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

The target <variable> and the source <expression> must be assignment compatible. The target variable must also have been declared. Since variables can store either primitive values or reference values, <expression> evaluates to either a primitive value or a reference value.

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 reference values by assignment creates aliases, which is discussed in Section 1.3, p. 6. 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 refers to the same pizza as pizza1, i.e., the hot and spicy one. After the assignment, these variables are aliases and either one can be used to manipulate the hot and spicy Pizza object.

Assigning a reference value does not create a copy of the source object denoted by the reference variable on the right-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. 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.

A more detailed discussion of reference assignment can be found in Section 7.8, p. 319.

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 righthand 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;           // (1)

What is the value of index, and which array element a[index] is assigned a value in the multiple assignment statement at (1)? 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.

Type Conversions in Assignment Context

If the target and 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, i.e., the source type is converted to the target type in an assignment context.

      // Widening Primitive Conversions
      int    smallOne = 1234;
      long   bigOne   = 2000;               // 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 converting 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 either byte, short, char, or int type

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

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

Here are some examples to illustrate how these conditions effect narrowing primitive conversions:

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

All other narrowing primitive conversions will produce a compile-time error on assignment and will explicitly require a cast. Here are some examples:

      // Above conditions not fulfilled for implicit narrowing primitive conversions.
      // A cast is 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.

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  minute = (byte) small;                // (4) short to byte.
      char  symbol = (char) 112.5F;               // (5) float to char.

Table 5.3 shows how the values are truncated for assignments from (1) to (5).

Table 5.3 Examples of Truncated Values

Examples of Truncated Values

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

The following examples illustrate boxing and unboxing in assignment context:

      Boolean   boolRef = true;  // boxing
      Byte      bRef    = 2;     // constant in range: narrowing to byte, then boxing
      //  Byte  bRef2   =  257;  // constant not in range: cast required
      Integer iRef3 = (short)10; // constant in range: casting by narrowing to short,
                                 // widening to int, then boxing

      short s = 10;              // narrowing
      // Integer   iRef1 = s;    // short not assignable to Integer

      boolean bv1 = boolRef;     // unboxing
      byte b1 = bRef;            // unboxing

      Integer iRefVal = null;           // Always allowed.
      int     j = iRefVal;              // NullPointerException!
      if (iRefVal != null) j = iRefVal; // Avoids the exception

Review Questions

Review Questions

5.1 Given the following declaration:

char c = 'A';

What is the simplest way to convert the character value in c into an int?

Select the one correct answer.

(a) int i = c;

(b) int i = (int) c;

(c) int i = Character.getNumericValue(c);

5.2 What will be the result of compiling and running the following program?

public class Assignment {
        public static void main(String[] args) {
          int a, b, c;
          b = 10;
          a = b = c = 20;
          System.out.println(a);
        }
      }

Select the one correct answer.

(a) The program will fail to compile since the compiler will report that the variable c in the multiple assignment statement a = b = c = 20; has not been initialized.

(b) The program will fail to compile, because the multiple assignment statement a = b = c = 20; is illegal.

(c) The code will compile and print 10, when run.

(d) The code will compile and print 20, when run.

5.3 What will be the result of compiling and running the following program?

public class MyClass {
        public static void main(String[] args) {
          String a, b, c;
          c = new String("mouse");
          a = new String("cat");
          b = a;
          a = new String("dog");
          c = b;
      
          System.out.println(c);
        }
      }

Select the one correct answer.

(a) The program will fail to compile.

(b) The program will print mouse, when run.

(c)The program will print cat, when run.

(d) The program will print dog, when run.

(e) The program will randomly print either cat or dog, when run.

5.6 Arithmetic Operators: *, /, %, +, -

Arithmetic operators are used to construct mathematical expressions as in algebra. Their operands are of numeric type (which includes the char type).

Arithmetic Operator Precedence and Associativity

In Table 5.4, the precedence of the operators is in decreasing order, starting from the top row, which has the highest precedence. Unary subtraction has higher precedence than multiplication. The operators in the same row have the same precedence. Binary multiplication, division, and remainder operators have the same precedence. The unary operators have right associativity, and the binary operators have left associativity.

Table 5.4 Arithmetic Operators

Arithmetic Operators

Evaluation Order in Arithmetic Expressions

Java guarantees that the operands are fully evaluated from left to right before an arithmetic binary operator is applied. If evaluation of an operand results in an error, the subsequent operands will not be evaluated.

In the expression a + b * c, the operand a will always be fully evaluated before the operand b, which will always be fully evaluated before the operand c. However, the multiplication operator * will be applied before the addition operator +, respecting the precedence rules. Note that a, b, and c are arbitrary arithmetic expressions that have been determined to be the operands of the operators.

The evaluation order and precedence rules for arithmetic expressions are illustrated in Example 5.1. The evaluation of each operand in the expression at (1) results in a call of the operandEval() method declared at (2). The first argument to this method is a number to identify the operand and the second argument is the operand value which is returned by the method. The output from the program shows that all three operands were evaluated from left to right and the value of the variable i shows that the precedence rules were applied in the evaluation.

Example 5.1 Operand Evaluation Order

public class OperandEvaluationOrder {
        public static void main(String[] args) {
          // Evaluate: 4 + 5 * 6
          int i = operandEval(1, 4) + operandEval(2, 5) * operandEval(3, 6);  // (1)
          System.out.println();
          System.out.println("Value of i: " + i);
        }

        static int operandEval(int opNum, int operand) {                      // (2)
          System.out.print(opNum);
          return operand;
        }
      }

Output from the program:

123
Value of i: 34

Range of Numeric Values

As we have seen, all numeric types have a range of valid values (Section 2.2, p. 28). This range is given by the constants named MAX_VALUE and MIN_VALUE, which are defined in each numeric wrapper class.

The arithmetic operators are overloaded, meaning that the operation of an operator varies depending on the type of its operands. Floating-point arithmetic is performed if any operand of an operator is of floating-point type, otherwise, integer arithmetic is performed.

Values that are out-of-range or are the results of invalid expressions are handled differently depending on whether integer or floating-point arithmetic is performed.

Integer Arithmetic

Integer arithmetic always returns a value that is in range, except in the case of integer division by zero and remainder by zero, which causes an ArithmeticException (see the division operator / and the remainder operator % below). A valid value does not necessarily mean that the result is correct, as demonstrated by the following examples:

int tooBig   = Integer.MAX_VALUE + 1;  // -2147483648 which is Integer.MIN_VALUE.
int tooSmall = Integer.MIN_VALUE - 1;  //  2147483647 which is Integer.MAX_VALUE.

The results above should be values that are out-of-range. However, integer arithmetic wraps if the result is out-of-range, i.e., the result is reduced modulo in the range of the result type. In order to avoid wrapping of out-of-range values, programs should either use explicit checks or a wider type. If the type long is used in the examples above, the results would be correct in the long range:

long notTooBig   = Integer.MAX_VALUE + 1L;  //  2147483648L in range.
long notTooSmall = Integer.MIN_VALUE - 1L;  // -2147483649L in range.

Floating-Point Arithmetic

Certain floating-point operations result in values that are out-of-range. Typically, adding or multiplying two very large floating-point numbers can result in an out-of-range value which is represented by Infinity (see Figure 5.2). Attempting floating-point division by zero also returns infinity. The examples below show how this value is printed as signed infinity.

System.out.println( 4.0 / 0.0);      // Prints: Infinity
System.out.println(-4.0 / 0.0);      // Prints: -Infinity

Figure 5.2 Overflow and Underflow in Floating-point Arithmetic

Overflow and Underflow in Floating-point Arithmetic

Both positive and negative infinity represent overflow to infinity, that is, the value is too large to be represented as a double or float (see Figure 5.2). Signed infinity is represented by named constants POSITIVE_INFINITY and NEGATIVE_INFINITY in the wrapper classes java.lang.Float and java.lang.Double. A value can be compared with these constants to detect overflow.

Floating-point arithmetic can also result in underflow to zero, i.e., the value is too small to be represented as a double or float (see Figure 5.2). Underflow occurs in the following situations:

the result is between Double.MIN_VALUE (or Float.MIN_VALUE) and zero; e.g., the result of (5.1E-324 - 4.9E-324). Underflow then returns positive zero 0.0 (or 0.0F).

• the result is between -Double.MIN_VALUE (or -Float.MIN_VALUE) and zero; e.g., the result of (-Double.MIN_VALUE * 1E-1). Underflow then returns negative zero -0.0 (or -0.0F).

Negative zero compares equal to positive zero, i.e., (-0.0 == 0.0) is true.

Certain operations have no mathematical result, and are represented by NaN (Not a Number). For example, calculating the square root of -1. Another example is (floating-point) dividing zero by zero:

System.out.println(0.0 / 0.0);      // Prints: NaN

NaN is represented by the constant named NaN in the wrapper classes java.lang.Float and java.lang.Double. Any operation involving NaN produces NaN. Any comparison (except inequality !=) involving NaN and any other value (including NaN) returns false. An inequality comparison of NaN with another value (including NaN) always returns true. However, the recommended way of checking a value for NaN is to use the static method isNaN() defined in both wrapper classes, java.lang.Float and java.lang.Double.

Strict Floating-Point Arithmetic: strictfp

Although floating-point arithmetic in Java is defined in accordance with the IEEE-754 32-bit (float) and 64-bit (double) standard formats, the language does allow JVM implementations to use other extended formats for intermediate results. This means that floating-point arithmetic can give different results on such JVMs, with possible loss of precision. Such a behavior is termed non-strict, in contrast to being strict and adhering to the standard formats.

To ensure that identical results are produced on all JVMs, the keyword strictfp can be used to enforce strict behavior for floating-point arithmetic. The modifier strictfp can be applied to classes, interfaces, and methods. A strictfp method ensures that all code in the method is executed strictly. If a class or interface is declared to be strictfp, then all code (in methods, initializers, and nested classes and interfaces) is executed strictly. If the expression is determined to be in a strictfp construct, it is executed strictly. However, note that strictness is not inherited by the subclasses or subinterfaces. Constant expressions are always evaluated strictly at compile time.

Unary Arithmetic Operators: -, +

The unary operators have the highest precedence of all the arithmetic operators. The unary operator - negates the numeric value of its operand. The following example illustrates the right associativity of the unary operators:

int value = - -10;       // (-(-10)) is 10

Notice the blank needed to separate the unary operators; otherwise, these would be interpreted as the decrement operator -- (see Section 5.8, p. 186). The unary operator + has no effect on the evaluation of the operand value.

Section G.4 on page 1010 discusses how negative integers are represented using 2’s complement.

Multiplicative Binary Operators: *, /, %

Multiplication Operator: *

The multiplication operator * multiplies two numbers.

int    sameSigns     = -4  * -8;     // result: 32
double oppositeSigns = 4.0 * -8.0;   // result: -32.0
int zero             = 0 * -0;       // result: 0

Division Operator: /

The division operator / is overloaded. If its operands are integral, the operation results in integer division.

int    i1 = 4  / 5;   // result: 0
int    i2 = 8  / 8;   // result: 1
double d1 = 12 / 8;   // result: 1.0, integer division, then widening conversion.

Integer division always returns the quotient as an integer value, i.e., the result is truncated toward zero. Note that the division performed is integer division if the operands have integral values, even if the result will be stored in a floating-point type. The integer value is subjected to a widening conversion in the assignment context.

An ArithmeticException is thrown when attempting integer division with zero, meaning that integer division by zero is an illegal operation.

If any of the operands is a floating-point type, the operation performs floating-point division, where relevant operand values undergo binary numeric promotion:

double d2 = 4.0 / 8;      // result: 0.5
double d3 = 8 / 8.0;      // result: 1.0
double d4 = 12.0F / 8;    // result: 1.5F

double result1 = 12.0 / 4.0 * 3.0;       // ((12.0 / 4.0) * 3.0) which is 9.0
double result2 = 12.0 * 3.0 / 4.0;       // ((12.0 * 3.0) / 4.0) which is 9.0

Remainder Operator: %

In mathematics, when we divide a number (the dividend) by another number (the divisor), the result can be expressed in terms of a quotient and a remainder. For example, dividing 7 by 5, the quotient is 1 and the remainder is 2. The remainder operator % returns the remainder of the division performed on the operands.

int quotient  = 7 / 5;   // Integer division operation: 1
int remainder = 7 % 5;   // Integer remainder operation: 2

For integer remainder operation, where only integer operands are involved, evaluation of the expression (x % y) always satisfies the following relation:

x == (x / y) * y + (x % y)

In other words, the right-hand side yields a value that is always equal to the value of the dividend. The following examples show how we can calculate the remainder so that the above relation is satisfied:

Calculating (7 % 5):

7 == (7 / 5) * 5 + (7 % 5)
   == (  1  ) * 5 + (7 % 5)
   ==           5 + (7 % 5)
2 ==               (7 % 5)            i.e., (7 % 5) is equal to 2

Calculating (7 % -5):

7 == (7 / -5) * -5 + (7 % -5)
   == (  -1  ) * -5 + (7 % -5)
   ==             5 + (7 % -5)
2 ==                 (7 % -5)         i.e., (7 % -5) is equal to 2

Calculating (-7 % 5):

-7 == (-7 / 5) * 5 + (-7 % 5)
    == (  -1  ) * 5 + (-7 % 5)
    ==           -5 + (-7 % 5)
-2 ==                (-7 % 5)            i.e., (-7 % 5) is equal to -2

Calculating (-7 % -5):

-7 == (-7 / -5) * -5 + (-7 % -5)
    == (   1   ) * -5 + (-7 % -5)
    ==             -5 + (-7 % -5)
-2 ==                  (-7 % -5)         i.e., (-7 % -5) is equal to -2

The above relation shows that the remainder can only be negative if the dividend is negative, and the sign of the divisor is irrelevant. A shortcut to evaluating the remainder involving negative operands is the following: ignore the signs of the operands, calculate the remainder, and negate the remainder if the dividend is negative.

int  r0 =  7  %  7;      //  0
int  r1 =  7  %  5;      //  2
long r2 =  7L % -5L;     //  2L
int  r3 = -7  %  5;      // -2
long r4 = -7L % -5L;     // -2L
boolean relation = -7L  == (-7L / -5L) * -5L + r4;  // true

An ArithmeticException is thrown if the divisor evaluates to zero.

Note that the remainder operator not only accepts integral operands, but floating-point operands as well. The floating-point remainder r is defined by the relation:

r == a - (b * q)

where a and b are the dividend and the divisor, respectively, and q is the integer quotient of (a/b). The following examples illustrate a floating-point remainder operation:

double  dr0 =  7.0  %  7.0;    //  0.0
float   fr1 =  7.0F %  5.0F;   //  2.0F
double  dr1 =  7.0  % -5.0;    //  2.0
float   fr2 = -7.0F %  5.0F;   // -2.0F
double  dr2 = -7.0  % -5.0;    // -2.0
boolean fpRelation = dr2  == (-7.0) - (-5.0) * (long)(-7.0 / -5.0);  // true
float   fr3 = -7.0F %  0.0F;   // NaN

Additive Binary Operators: +, -

The addition operator + and the subtraction operator - behave as their names imply: add or subtract values. The binary operator + also acts as string concatenation if any of its operands is a string (see Section 5.7, p. 185).

Additive operators have lower precedence than all the other arithmetic operators. Table 5.5 includes examples that show how precedence and associativity are used in arithmetic expression evaluation.

Table 5.5 Examples of Arithmetic Expression Evaluation

Examples of Arithmetic Expression Evaluation

Numeric Promotions in Arithmetic Expressions

Unary numeric promotion is applied to the single operand of the unary arithmetic operators - and +. When a unary arithmetic operator is applied to an operand whose type is narrower than int, the operand is promoted to a value of type int, with the operation resulting in an int value. If the conditions for implicit narrowing conversion are not fulfilled (p. 171), assigning the int result to a variable of a narrower type will require a cast. This is demonstrated by the following example, where the byte operand b is promoted to an int in the expression (-b):

byte b = 3;         // int literal in range. Narrowing conversion.
b = (byte) -b;      // Cast required on assignment.

Binary numeric promotion is applied to operands of binary arithmetic operators. Its application leads to type promotion for the operands, as explained in Section 5.2, p. 165. The result is of the promoted type, which is always type int or wider. For the expression at (1) in Example 5.2, numeric promotions proceed as shown in Figure 5.3. Note the integer division performed in evaluating the subexpression (c / s).

Example 5.2 Numeric Promotion in Arithmetic Expressions

public class NumPromotion {
  public static void main(String[] args) {
    byte   b = 32;
    char   c = 'z';                // Unicode value 122 (u007a)
    short  s = 256;
    int    i = 10000;
    float  f = 3.5F;
    double d = 0.5;
    double v = (d * i) + (f * - b) - (c / s);     // (1) 4888.0D
    System.out.println("Value of v: " + v);
  }
}

Output from the program:

Value of v: 4888.0

Figure 5.3 Numeric Promotion in Arithmetic Expressions

Numeric Promotion in Arithmetic Expressions

In addition to the binary numeric promotions in arithmetic expression evaluation, the resulting value can undergo an implicit widening conversion if assigned to a variable. In the first two declaration statements below, only assignment conversions take place. Numeric promotions take place in the evaluation of the righthand expression in the other declaration statements.

Byte   b = 10;        // constant in range: narrowing and boxing on assignment.
Short  s = 20;        // constant in range: narrowing and boxing on assignment.
char   c = 'z';       // 122 (u007a)
int    i = s * b;     // Values in s and b promoted to int: unboxing, widening
long   n = 20L + s;   // Value in s promoted to long: unboxing, widening
float  r = s + c;     // Values in s and c promoted to int, followed by implicit
                      // widening conversion of int to float on assignment.
double d = r + i;     // value in i promoted to float, followed by implicit
                      // widening conversion of float to double on assignment.

Binary numeric promotion for operands of binary operators implies that each operand of a binary operator is promoted to type int or a broader numeric type, if necessary. As with unary operators, care must be exercised in assigning the value resulting from applying a binary operator to operands of these types.

short h = 40;          // OK: int converted to short. Implicit narrowing.
h = h + 2;             // Error: cannot assign an int to short.

The value of expression h + 2 is of type int. Although the result of the expression is in the range of short, this cannot be determined at compile time. The assignment requires a cast.

h = (short) (h + 2); // OK

Notice that applying the cast operator (short) to the individual operands does not work:

h = (short) h + (short) 2;   // The resulting value should be cast.

In this case, binary numeric promotion leads to an int value as the result of evaluating the expression on the right-hand side and, therefore, requires an additional cast to narrow it to a short value.

Arithmetic Compound Assignment Operators: *=, /=, %=, +=, -=

A compound assignment operator has the following syntax:

<variable> <op<= <expression>

and the following semantics:

<variable> = (<type>) ((<variable>) <op> (<expression>))

The type of the <variable> is <type> and the <variable> is evaluated only once. Note the cast and the parentheses implied in the semantics. Here <op>= can be any of the compound assignment operators specified in Table 5.2. The compound assignment operators have the lowest precedence of all the operators in Java, allowing the expression on the right-hand side to be evaluated before the assignment. Table 5.4 defines the arithmetic compound assignment operators.

Table 5.6 Arithmetic Compound Assignment Operators

Arithmetic Compound Assignment Operators

The implied cast operator, (T), in the compound assignments becomes necessary when the result must be narrowed to the target type. This is illustrated by the following examples:

int i = 2;
i *= i + 4;             // (1) Evaluated as i = (int) ((i) * (i + 4)).

Integer iRef = 2;
iRef *= iRef + 4;       // (2) Evaluated as iRef = (Integer) ((iRef) * (iRef + 4)).

byte b = 2;
b += 10;                // (3) Evaluated as b = (byte) (b + 10).
b = b + 10;             // (4) Will not compile. Cast is required.

At (1) the source int value is assigned to the target int variable, and the cast operator (int) in this case is an identity conversion (i.e., conversion from a type to the same type). Such casts are permitted. The assignment at (2) entails unboxing to evaluate the expression on the right-hand side, followed by boxing to assign the int value. However, at (3), as the source value is an int value because the byte value in b is promoted to int to carry out the addition, assigning it to a target byte variable requires an implicit narrowing conversion. The situation at (4) with simple assignment will not compile, because implicit narrowing conversion is not applicable.

The <variable> is only evaluated once in the expression, not twice, as one might infer from the definition of the compound assignment operator. In the following assignment, a[i] is only evaluated once:

int[] a = new int[] { 2008, 2009, 2010 };
int i = 2;
a[i] += 1;      // evaluates as a[2] = a[2] + 1, and a[2] gets the value 2011.

Implicit narrowing conversions are also applied for increment and decrement operators (see Section 5.8, p. 186).

Other compound assignment operators include boolean logical, bitwise, and shift operators—of which, only the boolean logical operators are discussed in this book (see Section 5.12, p. 194).

Review Questions

Review Questions

5.4 Which of the following expressions will be evaluated using floating-point arithmetic?

Select the three correct answers.

(a) 2.0 * 3.0

(b) 2 * 3

(c) 2/3 + 5/7

(d) 2.4 + 1.6

(e) 0x10 * 1L * 300.0

5.5 What is the value of the expression (1 / 2 + 3 / 2 + 0.1)?

Select the one correct answer.

(a) 1

(b) 1.1

(c) 1.6

(d) 2

(e) 2.1

5.6 What will be the result of compiling and running the following program?

public class Integers {
  public static void main(String[] args) {
    System.out.println(0x10 + 10 + 010);
  }
}

Select the one correct answer.

(a) The program will not compile because of errors in the expression 0x10 + 10 + 010.

(b) When run, the program will print 28.

(c) When run, the program will print 30.

(d) When run, the program will print 34.

(e) When run, the program will print 36.

(f) When run, the program will print 101010.

5.7 Which of the following expressions are valid?

Select the three correct answers.

(a) (- 1 -)

(b) (+ + 1)

(c) (+-+-+-1)

(d) (--1)

(e) (1 * * 1)

(f) (- -1)

5.8 What is the value of evaluating the following expression (- -1-3 * 10 / 5-1)?

Select the one correct answer.

(a)–8

(b)–6

(c) 7

(d) 8

(e) 10

(f) None of the above.

5.9 Which of these assignments are valid?

Select the four correct answers.

(a) short s = 12;

(b) long l = 012;

(c) int other = (int) true;

(d) float f = -123;

(e) double d = 0x12345678;

5.7 The Binary String Concatenation Operator +

The binary operator + is overloaded in the sense that the operation performed is determined by the type of the operands. When one of the operands is a String object, a string conversion is performed on the other operand, implicitly converting it to its string representation, before the string concatenation is performed. Non-String operands are converted as follows:

• For an operand of a primitive data type, its value is first converted to a reference value using the object creation expression. A string representation of the reference value is obtained as explained below for reference types.

• Values like true, false, and null are represented by string representations of these literals. A reference variable with the value null also has the string representation "null" in this context.

• For all reference value operands, a string representation is constructed by calling the toString() method on the referred object. Most classes override this method from the Object class in order to provide a more meaningful string representation of their objects. Discussion of the toString() method can be found in Section 10.2, p. 424.

The string concatenation operator + is left associative, and the result of the concatenation is always a new String object. The String class is discussed in Section 10.4, p. 439.

String theName = " Uranium";
theName = " Pure" + theName;                // " Pure Uranium"
String trademark1 = 100 + "%" + theName;    // "100% Pure Uranium" (1)

The integer literal 100 is implicitly converted to the string "100" before concatenation. This conversion corresponds to first creating an object of the wrapper class Integer, which boxes the integer 100, and then creating a string from this object by using the toString() method supplied by this class:

new Integer(100).toString();

Note that using the character literal '%', instead of the string literal "%" in line (1) above, does not give the same result:

String trademark2 = 100 + '%' + theName;   // "137 Pure Uranium"

Integer addition is performed by the first + operator: 100 + '%', that is, (100 + 37). Caution should be exercised as the + operator might not be applied as intended, as shown by the following example:

System.out.println("We put two and two together and get " + 2 + 2);

The above statement prints "We put two and two together and get 22" and not "We put two and two together and get 4". The first integer literal 2 is promoted to a String literal "2" for the first concatenation, resulting in the String literal "We put two and two together and get 2". This result is then concatenated with the String literal "2". The whole process proceeds as follows:

"We put two and two together and get "  +  2  +  2
"We put two and two together and get "  + "2" +  2
"We put two and two together and get 2"       +  2
"We put two and two together and get 2"       + "2"
"We put two and two together and get 22"

Both occurrences of the + operator are treated as string concatenation. To convey the intended meaning of the sentence, parentheses are highly recommended:

System.out.println("We put two and two together and get " + (2 + 2));

The compiler uses a string builder to avoid the overhead of temporary String objects when applying the string concatenation operator (+), as explained in Section 10.5, p. 460.

5.8 Variable Increment and Decrement Operators: ++, --

Variable increment (++) and decrement (--) operators come in two flavors: prefix and postfix. These unary operators have the side effect of changing the value of the arithmetic operand which must evaluate to a variable. Depending on the operator used, the variable is either incremented or decremented by 1.

These operators cannot be applied to a variable that is declared final and which has been initialized, as the side effect would change the value in such a variable.

These operators are very useful for updating variables in loops where only the side effect of the operator is of interest.

The Increment Operator ++

Prefix increment operator has the following semantics:

++i adds 1 to the value in i, and stores the new value in i. It returns the new value as the value of the expression. It is equivalent to the following statements:

i += 1;
result = i;
return result;

Postfix increment operator has the following semantics:

j++ adds 1 to the value in j, and stores the new value in j. It returns the old value in j before the new value is stored in j, as the value of the expression. It is equivalent to the following statements:

result = j;
j += 1;
return result;

The Decrement Operator --

Prefix decrement operator has the following semantics:

--i subtracts 1 from the value of i, and stores the new value in i. It returns the new value as the value of the expression.

Postfix decrement operator has the following semantics:

j-- subtracts 1 from the value of j, and stores the new value in j. It returns the old value in j before the new value is stored in j as the value of the expression.

The above discussion on decrement and increment operators applies to any variable whose type is a numeric primitive type or its corresponding numeric wrapper type. Necessary numeric promotions are performed on the value 1 and the value of the variable. Before assigning the new value to the variable, it is subjected to any narrowing primitive conversion and/or boxing that might be necessary.

Here are some examples to illustrate the behavior of increment and decrement operators:

// (1) Prefix order: increment/decrement operand before use.
int i = 10;
int k = ++i + --i;  // ((++i) + (--i)). k gets the value 21 and i becomes 10.
--i;                // Only side effect utilized. i is 9. (expression statement)

Integer iRef = 10;  // Boxing on assignment
k = ++iRef + --iRef;// ((++iRef) + (--iRef)). k gets the value 21 and
                    // iRef refers to an Integer object with the value 10.
--iRef;             // Only side effect utilized. iRef refers to an Integer
              // object with the value 9. (expression statement)

// (2) Postfix order: increment/decrement operand after use.
long i = 10;

long k = i++ + i--; // ((i++) + (i--)). k gets the value 21L and i becomes 10L.
i++;               // Only side effect utilized. i is 11L. (expression statement)

An increment or decrement operator, together with its operand, can be used as an expression statement (see Section 3.3, p. 45).

Execution of the assignment in the second declaration statement under (1) proceeds as follows:

k = ((++i) + (--i))        Operands are evaluated from left to right.
k = ( 11   + (--i))        Side-effect: i += 1, i gets the value 11.
k = ( 11   + 10)        Side-effect: i -= 1, i gets the value 10.
k = 21

Expressions where variables are modified multiple times during the evaluation should be avoided, because the order of evaluation is not always immediately apparent.

We cannot associate increment and decrement operators. Given that a is a variable, we cannot write (++(++a)). The reason is that any operand to ++ must evaluate to a variable, but the evaluation of (++a) results in a value.

In the example below, both binary numeric promotion and an implicit narrowing conversion are performed to achieve the side effect of modifying the value of the operand. The int value of the expression (++b) (that is, 11), is assigned to the int variable i. The side effect of incrementing the value of the byte variable b requires binary numeric promotion to perform int addition, followed by an implicit narrowing conversion of the int value to byte to perform the assignment.

byte b = 10;
int i = ++b;      // i is 11, and so is b.

The example below illustrates applying the increment operator to a floating-point operand. The side effect of the ++ operator is overwritten by the assignment.

double x = 4.5;
x = x + ++x;      // x gets the value 10.0.

Review Questions

Review Questions

5.10 Which statements are true?

Select the three correct answers.

(a) The expression (1 + 2 + "3") evaluates to the string "33".

(b) The expression ("1" + 2 + 3) evaluates to the string "15".

(c) The expression (4 + 1.0f) evaluates to the float value 5.0f.

(d) The expression (10/9) evaluates to the int value 1.

(e) The expression ('a' + 1) evaluates to the char value 'b'.

5.11 What happens when you try to compile and run the following program?

public class Prog1 {
  public static void main(String[] args) {
    int k = 1;
    int i = ++k + k++ + + k;     // (1)
    System.out.println(i);
  }
}

Select the one correct answer.

(a) The program will not compile, because of errors in the expression at (1).

(b) The program will compile and print the value 3, when run.

(c) The program will compile and print the value 4, when run.

(d) The program will compile and print the value 7, when run.

(e) The program will compile and print the value 8, when run.

5.12 What is the label of the first line that will cause a compile time error in the following program?

public class MyClass {
  public static void main(String[] args) {
    char c;
    int i;
    c = 'a'; // (1)
    i = c;   // (2)
    i++;     // (3)
    c = i;   // (4)
    c++;     // (5)
  }
}

Select the one correct answer.

(a) (1)

(b) (2)

(c) (3)

(d) (4)

(e) (5)

(f) None of the above. The compiler will not report any errors.

5.13 What is the result of compiling and running the following program?

public class Cast {
  public static void main(String[] args) {
    byte b = 128;
    int i = b;
    System.out.println(i);
  }
}

Select the one correct answer.

(a) The program will not compile because a byte value cannot be assigned to an int variable without using a cast.

(b) The program will compile and print 128, when run.

(c) The program will not compile because the value 128 is not in the range of values for the byte type.

(d) The program will compile but will throw a ClassCastException when run.

(e) The program will compile and print 255, when run.

5.14 What will be the result of compiling and running the following program?

public class EvaluationOrder {
  public static void main(String[] args) {
    int[] array = { 4, 8, 16 };
    int i=1;
    array[++i] = --i;
    System.out.println(array[0] + array[1] + array[2]);
      #160; }
}

Select the one correct answer.

(a) 13

(b) 14

(c) 20

(d) 21

(e) 24

5.9 Boolean Expressions

As the name implies, a boolean expression has the boolean data type and can only evaluate to the values true or false.

Boolean expressions, when used as conditionals in control statements, allow the program flow to be controlled during execution.

Boolean expressions can be formed using relational operators (Section 5.10, p. 190), equality operators (Section 5.11, p. 191), bitwise operators (which are not covered in this book), boolean logical operators (Section 5.12, p. 194), conditional operators (Section 5.13, p. 196), the assignment operator (Section 5.5, p. 169), and the instanceof operator (Section 7.11, p. 328).

5.10 Relational Operators: <, <=, >, >=

Given that a and b represent numeric expressions, the relational (also called comparison) operators are defined as shown in Table 5.7.

Table 5.7 Relational Operators

a < b a less than b?
a <= b a less than or equal to b?
a > b a greater than b?
a >= b a greater than or equal to b?

All relational operators are binary operators and their operands are numeric expressions. Binary numeric promotion is applied to the operands of these operators. The evaluation results in a boolean value. Relational operators have precedence lower than arithmetic operators, but higher than that of the assignment operators.

double  hours = 45.5;
boolean overtime = hours >= 35.0;    // true.
boolean order = 'A' < 'a';           // true. Binary numeric promotion applied.
Double time = 18.0;
boolean beforeMidnight = time < 24.0;// true. Binary numeric promotion applied.

Relational operators are nonassociative. Mathematical expressions like abc must be written using relational and boolean logical/conditional operators.

int a = 1, b = 7, c = 10;
boolean valid1 = a <= b <= c;             // (1) Illegal.
boolean valid2 = a <= b && b <= c;        // (2) OK.

Since relational operators have left associativity, the evaluation of the expression a <= b <= c at (1) in the examples above would proceed as follows: ((a <= b) <= c). Evaluation of (a <= b) would yield a boolean value that is not permitted as an operand of a relational operator, i.e., (<boolean value> <= c) would be illegal.

5.11 Equality

We distinguish between primitive data equality, object reference equality, and object value equality.

The equality operators have lower precedence than the relational operators, but higher than that of the assignment operators.

Primitive Data Value Equality: ==, !=

Given that a and b represent operands of primitive data types, the primitive data value equality operators are defined as shown in Table 5.8.

Table 5.8 Primitive Data Value Equality Operators

a == b Determines whether a and b are equal, i.e., have the same primitive value. (Equality)
a != b Determines whether a and b are not equal, i.e., do not have the same primitive value. (Inequality)

The equality operator == and the inequality operator != can be used to compare primitive data values, including boolean values. Binary numeric promotion is applied to the nonboolean operands of these equality operators.

int year = 2002;
boolean isEven  = year % 2 == 0;      // true.
boolean compare = '1' == 1;           // false. Binary numeric promotion applied.
boolean test    = compare == false;   // true.

Care must be exercised in comparing floating-point numbers for equality, as an infinite number of floating-point values can be stored in a finite number of bits only as approximations. For example, the expression (1.0 - 2.0/3.0 == 1.0/3.0) returns false, although mathematically the result should be true.

Analogous to the discussion for relational operators, mathematical expressions like a = b = c must be written using relational and logical/conditional operators. Since equality operators have left associativity, the evaluation of the expression a == b == c would proceed as follows: ((a == b) == c). Evaluation of (a == b) would yield a boolean value that is permitted as an operand of a data value equality operator, but (<boolean value> == c) would be illegal if c had a numeric type. This problem is illustrated in the examples below. The expression at (1) is illegal, but those at (2) and (3) are legal.

int a, b, c;
a = b = c = 5;
boolean valid1 = a == b == c;               // (1) Illegal.
boolean valid2 = a == b && b == c;          // (2) Legal.
boolean valid3 = a == b == true;            // (3) Legal.

Object Reference Equality: ==, !=

The equality operator == and the inequality operator != can be applied to reference variables to test whether they refer to the same object. Given that r and s are reference variables, the reference equality operators are defined as shown in Table 5.9.

Table 5.9 Reference Equality Operators

r == s Determines whether r and s are equal, i.e., have the same reference value and therefore refer to the same object (also called aliases). (Equality)
r != s Determines whether r and s are not equal, i.e., do not have the same reference value and therefore refer to different objects. (Inequality)

The operands must be cast compatible: it must be possible to cast the reference value of the one into the other’s type; otherwise, it is a compile-time error. Casting of references is discussed in Section 7.8, p. 319.

Pizza pizza_A = new Pizza("Sweet&Sour");       // new object
Pizza pizza_B = new Pizza("Sweet&Sour");       // new object
Pizza pizza_C = new Pizza("Hot&Spicy");        // new object

String banner = "Come and get it!";            // new object

boolean test  = banner  == pizza_A;            // (1) Compile-time error.
boolean test1 = pizza_A == pizza_B;            // false
boolean test2 = pizza_A == pizza_C;            // false

pizza_A = pizza_B;                         // Denote the same object, are aliases.
boolean test3 = pizza_A == pizza_B;        // true

The comparison banner == pizza_A in (1) is illegal, because the String and Pizza types are not related and therefore the reference value of one type cannot be cast to the other type. The values of test1 and test2 are false because the three references denote different objects, regardless of the fact that pizza_A and pizza_B are both sweet and sour pizzas. The value of test3 is true because now both pizza_A and pizza_B denote the same object.

The equality and inequality operators are applied to object references to check whether two references denote the same object or not. The state of the objects that the references denote is not compared. This is the same as testing whether the references are aliases, i.e., denoting the same object.

The null literal can be assigned to any reference variable, and the reference value in a reference variable can be compared for equality with the null literal. The comparison can be used to avoid inadvertent use of a reference variable that does not denote any object.

if (objRef != null) {
    // ... use objRef ...
}

Note that only when the type of both operands is either a reference type or the null type, do these operators test for object reference equality. Otherwise, they test for primitive data equality (see also Section 10.3, p. 432). In (2) below, binary numeric promotion involving unboxing is performed.

Integer iRef = 10;
test for primitive data equality boolean b1 = iRef == null;       // (1) object reference equality
test for primitive data equality boolean b2 = iRef == 10;         // (2) primitive data equality

Object Value Equality

The Object class provides the method public boolean equals(Object obj), which can be overridden (see Section 7.2, p. 288) to give the right semantics of object value equality. The default implementation of this method in the Object class returns true only if the object is compared with itself, i.e., as if the equality operator == had been used to compare aliases of an object. This means that if a class does not override the semantics of the equals() method from the Object class, object value equality is the same as object reference equality. For a detailed discussion on implementing the equals() method, see Section 15.1, p. 751.

Certain classes in the standard API override the equals() method, e.g., java.lang.String, java.util.Date, java.io.File and the wrapper classes for the primitive data types. For two String objects, value equality means they contain identical character sequences. For the wrapper classes, value equality means that the primitive values in the two wrapper objects are equal (see also Section 10.3, p. 432).

// Equality for String objects means identical character sequences.
String movie1 = new String("The Revenge of the Exception Handler");
String movie2 = new String("High Noon at the Java Corral");
String movie3 = new String("The Revenge of the Exception Handler");
boolean test0 = movie1.equals(movie2);             // false
boolean test1 = movie1.equals(movie3);             // true

// Equality for Boolean objects means same primitive value
Boolean flag1 = true;
Boolean flag2 = false;
boolean test2 = flag1.equals(flag2);               // false

// The Pizza class does not override the equals() method,
// can use either equals() inherited from Object or ==.
Pizza pizza1 = new Pizza("VeggiesDelight");
Pizza pizza2 = new Pizza("VeggiesDelight");
Pizza pizza3 = new Pizza("CheeseDelight");
boolean test3 = pizza1.equals(pizza2);             // false
boolean test4 = pizza1.equals(pizza3);             // false
boolean test5 = pizza1 == pizza2;                  // false
pizza1 = pizza2;                                   // Creates aliases
boolean test7 = pizza1.equals(pizza2);             // true
boolean test6 = pizza1 == pizza2;                  // true

5.12 Boolean Logical Operators: !, ^, &, |

Boolean logical operators include the unary operator ! (logical complement) and the binary operators & (logical AND), | (logical inclusive OR), and ^ (logical exclusive OR, also called logical XOR). Boolean logical operators can be applied to boolean or Boolean operands, returning a boolean value. The operators &, |, and ^ can also be applied to integral operands to perform bitwise logical operations (which are not covered in this book).

Given that x and y represent boolean expressions, the boolean logical operators are defined in Table 5.10.

Table 5.10 Truth-Values for Boolean Logical Operators

Truth-Values for Boolean Logical Operators

These operators always evaluate both the operands, unlike their counterpart conditional operators && and || (see Section 5.13, p. 196). Unboxing is applied to the operand values, if necessary. Truth-values for boolean logical operators are shown in Table 5.10.

Operand Evaluation for Boolean Logical Operators

In the evaluation of boolean expressions involving boolean logical AND, XOR, and OR operators, both the operands are evaluated. The order of operand evaluation is always from left to right.

if (i > 0 & i++ < 10) {/*...*/} // i will be incremented, regardless of value in i.

The binary boolean logical operators have precedence lower than arithmetic and relational operators, but higher than assignment, conditional AND, and OR operators (see Section 5.13, p. 196). This is illustrated in the following examples:

boolean b1, b2, b3 = false, b4 = false;
Boolean b5 = true;
b1 = 4 == 2 & 1 < 4;           // false, evaluated as (b1 = ((4 == 2) & (1 < 4)))
b2 = b1 | !(2.5 >= 8);         // true
b3 = b3 ^ b5;                  // true, unboxing conversion on b5
b4 = b4 | b1 & b2;             // false

Order of evaluation is illustrated for the last example:

    (b4 = (b4 | (b1 & b2)))
⇒ (b4 = (false | (b1 & b2)))
⇒ (b4 = (false | (false & b2)))
⇒ (b4 = (false | (false & true)))
⇒ (b4 = (false | false))
⇒ (b4 = false)

Note that b2 was evaluated although, strictly speaking, it was not necessary. This behavior is guaranteed for boolean logical operators.

Boolean Logical Compound Assignment Operators: &=, ^=, |=

Compound assignment operators for the boolean logical operators are defined in Table 5.11. The left-hand operand must be a boolean variable, and the right-hand operand must be a boolean expression. An identity conversion is applied implicitly on assignment.

Table 5.11 Boolean Logical Compound Assignment Operators

Boolean Logical Compound Assignment Operators

See also the discussion on arithmetic compound assignment operators in Section 5.6, p. 182. Here are some examples to illustrate the behavior of boolean logical compound assignment operators:

boolean b1 = false, b2 = false, b3 = false;
Boolean b4 = false;
b1 |= true;             // true
b4 ^= b1;               // (1) true, unboxing in (b4 ^ b1), boxing on assignment
b3 &= b1 | b2;          // (2) false. b3 = (b3 & (b1 | b2)).
b3 = b3 & b1 | b2;      // (3) true.  b3 = ((b3 & b1) | b2).

The assignment at (1) entails unboxing to evaluate the expression on the righthand side, followed by boxing to assign the boolean result. It is also instructive to compare how the assignments at (2) and (3) above are performed, giving different results for the same value of the operands, showing how the precedence affects the evaluation.

5.13 Conditional Operators: &&, ||

The conditional operators && and || are similar to their counterpart logical operators & and |, except that their evaluation is short-circuited. Given that x and y represent values of boolean or Boolean expressions, the conditional operators are defined in Table 5.12. In the table, the operators are listed in decreasing precedence order.

Table 5.12 Conditional Operators

Conditional Operators

Unlike their logical counterparts & and |, which can also be applied to integral operands for bitwise operations, the conditional operators && and || can only be applied to boolean operands. Their evaluation results in a boolean value. Truthvalues for conditional operators are shown in Table 5.13. Not surprisingly, they have the same truth-values as their counterpart logical operators.

Table 5.13 Truth-values for Conditional Operators

Truth-values for Conditional Operators

Note that, unlike their logical counterparts, there are no compound assignment operators for the conditional operators.

Short-Circuit Evaluation

In evaluation of boolean expressions involving conditional AND and OR, the lefthand operand is evaluated before the right one, and the evaluation is shortcircuited (i.e., if the result of the boolean expression can be determined from the left-hand operand, the right-hand operand is not evaluated). In other words, the right-hand operand is evaluated conditionally.

The binary conditional operators have precedence lower than either arithmetic, relational, or logical operators, but higher than assignment operators. Unboxing of the operand value takes place when necessary, before the operation is performed. The following examples illustrate usage of conditional operators:

Boolean b1 = 4 == 2 && 1 < 4;   // false, short-circuit evaluated as
                          // (b1 = ((4 == 2) && (1 < 4)))
boolean b2 = !b1 || 2.5 > 8;    // true, short-circuit evaluated as
                          // (b2 = ((!b1) || (2.5 > 8)))
Boolean b3 = !(b1 && b2);       // true
boolean b4 = b1 || !b3 && b2;   // false, short-circuit evaluated as
                          // (b4 = (b1 || ((!b3) && b2)))

The order of evaluation for computing the value of boolean variable b4 proceeds as follows:

    (b4 = (b1 || ((!b3) && b2)))
⇒ (b4 = (false || ((!b3) && b2)))
⇒ (b4 = (false || ((!true) && b2)))
⇒ (b4 = (false || ((false) && b2)))
⇒ (b4 = (false || false))
⇒ (b4 = false)

Note that b2 is not evaluated, short-circuiting the evaluation. Example 5.3 illustrates the short-circuit evaluation of the initialization expressions in the declaration statements above. In addition, it shows an evaluation (see the declaration of b5) involving boolean logical operators that always evaluate both operands. See also Example 5.1 that uses a similar approach to illustrate the order of operand evaluation in arithmetic expressions.

Example 5.3 Short-Circuit Evaluation Involving Conditional Operators

public class ShortCircuit {
  public static void main(String[] args) {
    // Boolean b1 = 4 == 2 && 1 < 4;
    Boolean b1 = operandEval(1, 4 == 2) && operandEval(2, 1 < 4);
    System.out.println();
    System.out.println("Value of b1: " + b1);

    // boolean b2 = !b1 || 2.5 > 8;
    boolean b2 = !operandEval(1, b1) || operandEval(2, 2.5 > 8);
    System.out.println();
    System.out.println("Value of b2: " + b2);

    // Boolean b3 = !(b1 && b2);
    Boolean b3 = !(operandEval(1, b1) && operandEval(2, b2));
    System.out.println();
    System.out.println("Value of b3: " + b3);

    // boolean b4 = b1 || !b3 && b2;
    boolean b4 = operandEval(1, b1) || !operandEval(2, b3) && operandEval(3, b2);
    System.out.println();
    System.out.println("Value of b4: " + b4);

    // boolean b5 = b1 | !b3 & b2;    // Using boolean logical operators
    boolean b5 = operandEval(1, b1) | !operandEval(2, b3) & operandEval(3, b2);
    System.out.println();
    System.out.println("Value of b5: " + b5);
  }
  static boolean operandEval(int opNum, boolean operand) {                // (1)
    System.out.print(opNum);
    return operand;
  }
}

Output from the program:

1
Value of b1: false
1
Value of b2: true
1
Value of b3: true
12
Value of b4: false
123
Value of b5: false

Short-circuit evaluation can be used to ensure that a reference variable denotes an object before it is used.

if (objRef != null && objRef.doIt()) { /*...*/ }

The method call is now conditionally dependent on the left-hand operand and will not be executed if the variable objRef has the null reference. If we use the logical & operator and the variable objRef has the null reference, evaluation of the righthand operand will result in a NullPointerException.

In summary, we employ the conditional operators && and || if the evaluation of the right-hand operand is conditionally dependent on the left-hand operand. We use the boolean logical operators & and | if both operands must be evaluated. The subtlety of conditional operators is illustrated by the following examples:

if (i > 0 && i++ < 10) {/*...*/}   // i is not incremented if i > 0 is false.
if (i > 0 || i++ < 10) {/*...*/}   // i is not incremented if i > 0 is true.

Review Questions

Review Questions

5.15 Which of the following expressions evaluate to true?

Select the two correct answers.

(a) (false | true)

(b) (null != null)

(c) (4 <= 4)

(d) (!true)

(e) (true & false)

5.16 Which statements are true?

Select the two correct answers.

(a) The remainder operator % can only be used with integral operands.

(b) Short-circuit evaluation occurs with boolean logical operators.

(c) The arithmetic operators *, /, and % have the same level of precedence.

(d) A short value ranges from -128 to +127, inclusive.

(e) (+15) is a legal expression.

5.17 Which statements are true about the lines of output printed by the following program?

public class BoolOp {
  static void op(boolean a, boolean b) {
    boolean c = a != b;
    boolean d = a ^ b;
    boolean e = c == d;
    System.out.println(e);
  }

  public static void main(String[] args) {
    op(false, false);
    op(true, false);
    op(false, true);
    op(true, true);
  }
}

Select the three correct answers.

(a) All lines printed are the same.

(b) At least one line contains false.

(c) At least one line contains true.

(d) The first line contains false.

(e) The last line contains true.

5.18 What is the result of running the following program?

public class OperandOrder {
  public static void main(String[] args) {
    int i = 0;
    int[] a = {3,6};
    a[i] = i = 9;
    System.out.println(i + " " + a[0] + " " + a[1]);
  }
}

Select the one correct answer.

(a) When run, the program throws an exception of type ArrayIndexOutOfBoundsException.

(b) When run, the program will print "9 9 6".

(c) When run, the program will print "9 0 6".

(d) When run, the program will print "9 3 6".

(e) When run, the program will print "9 3 9".

5.19 Which statements are true about the output from the following program?

public class Logic {
  public static void main(String[] args) {
    int i = 0;
    int j = 0;

    boolean t = true;
    boolean r;

    r = (t & 0 < (i+=1));
    r = (t && 0 < (i+=2));
    r = (t | 0 < (j+=1));
    r = (t || 0 < (j+=2));
    System.out.println(i + " " + j);
  }
}

Select the two correct answers.

(a) The first digit printed is 1.

(b) The first digit printed is 2.

(c) The first digit printed is 3.

(d) The second digit printed is 1.

(e) The second digit printed is 2.

(f) The second digit printed is 3.

5.14 The Conditional Operator: ?:

The ternary conditional operator allows conditional expressions to be defined. The operator has the following syntax:

<condition> ? <expression>1 : <expression>2

If the boolean expression <condition> is true then <expression>1 is evaluated; otherwise, <expression>2 is evaluated. Of course, <expression>1 and <expression>2 must evaluate to values of compatible types. The value of the expression evaluated is returned by the conditional expression.

boolean leapYear = false;
int daysInFebruary = leapYear ? 29 : 28; // 28

The conditional operator is the expression equivalent of the if-else statement (Section 6.2, p. 205). The conditional expression can be nested and the conditional operator associates from right to left:

(a?b?c?d:e:f:g) evaluates as (a?(b?(c?d:e):f):g)

5.15 Other Operators: new, [], instanceof

The new operator is used to create objects, i.e., instances of classes and arrays. It is used with a constructor call to instantiate classes (see Section 3.4, p. 48), and with the [] notation to create arrays (see Section 3.6, p. 70). It is also used to instantiate anonymous arrays (see Section 3.6, p. 74), and anonymous classes (see Section 8.5, p. 377).

Pizza onePizza = new Pizza();      // Create an instance of the Pizza class.

The [] notation is used to declare and construct arrays and also to access array elements (see Section 3.6, p. 69).

int[] anArray = new int[5];// Declare and construct an int array of 5 elements.
anArray[4] = anArray[3];   // Element at index 4 gets value of element at index 3.

The boolean, binary, and infix operator instanceof is used to test the type of an object (see Section 7.11, p. 327).

Pizza myPizza = new Pizza();
boolean test1 = myPizza instanceof Pizza; // True.
boolean test2 = "Pizza" instanceof Pizza; // Compile error. String is not Pizza.
boolean test3 = null instanceof Pizza;    // Always false. null is not an instance.

Chapter Summary

Chapter Summary

The following information was included in this chapter:

• type conversion categories and conversion contexts, and which conversions are permissible in each conversion context.

• operators in Java, including precedence and associativity rules.

• defining and evaluating arithmetic and boolean expressions, and the order in which operands and operators are evaluated.

Programming Exercise

Programming Exercise

5.1 The program below is supposed to calculate and print the time it takes for light to travel from the sun to the earth. It contains some logical errors. Fix the program so that it will compile and print the correct result when run.

//Filename: Sunlight.java
public class Sunlight {
  public static void main(String[] args) {
    // Distance from sun (150 million kilometers)
    int kmFromSun = 150000000;

    int lightSpeed = 299792458; // meters per second

    // Convert distance to meters.
    int mFromSun = kmFromSun * 1000;

    int seconds = mFromSun / lightSpeed;

    System.out.print("Light will use ");
    printTime(seconds);
    System.out.println(" to travel from the sun to the earth.");
  }

  public static void printTime(int sec) {
    int min = sec / 60;
    sec = sec - (min * 60);
    System.out.print(min + " minute(s) and " + sec + " second(s)");
  }
}

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.217.7.174