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.
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
.
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.
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.
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.
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).
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.
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 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.
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 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 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 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)
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.
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.
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.
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.
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.
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.
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.
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))
Copying reference values by assignment creates aliases, which is discussed in Section 1.3, p. 6. The following example recapitulates that discussion:
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.
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.
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).
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
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.
Arithmetic operators are used to construct mathematical expressions as in algebra. Their operands are of numeric type (which includes the char
type).
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.
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
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 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.
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
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
.
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.
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.
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
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
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.
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
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.
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.
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).
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;
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.
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.
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;
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.
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
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).
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 a ≤ b ≤ c 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.
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.
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.
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.
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
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
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.
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.
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.
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.
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.
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.
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.
Note that, unlike their logical counterparts, there are no compound assignment operators for the conditional operators.
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.
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.
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)
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.
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.
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)");
}
}