All Categories :
Java
Chapter 7
Expressions, Operators, and Control
Structures
by Michael Morrison
CONTENTS
In the previous chapter, you learned about the basic components
of a Java program. This chapter focuses on how to use these components
to do more useful things. Data types are interesting, but without
expressions and operators, you can't do much with them. Even expressions
and operators alone are somewhat limited in what they can do.
Throw in control structures and you have the ability to do some
interesting things.
This chapter covers all these issues and pulls together many of
the missing pieces of the Java programming puzzle you've begun
to assemble. You'll not only expand your knowledge of the Java
language a great deal, you'll also learn what it takes to write
some more interesting programs.
Once you create variables, you typically want to do something
with them. Operators enable you to perform an evaluation
or computation on a data object or objects. Operators applied
to variables and literals form expressions. An expression
can be thought of as a programmatic equation. More formally, an
expression is a sequence of one or more data objects (operands)
and zero or more operators that produce a result. An example of
an expression follows:
x = y / 3;
In this expression, x and
y are variables, 3
is a literal, and = and /
are operators. This expression states that the y
variable is divided by 3
using the division operator (/),
and the result is stored in x
using the assignment operator (=).
Notice that the expression was described from right to left. Although
this approach of analyzing the expression from right to left is
useful in terms of showing the assignment operation, most Java
expressions are, in fact, evaluated from left to right. You get
a better feel for this in the next section.
Operator Precedence
Even though Java expressions are typically evaluated from left
to right, there still are many times when the result of an expression
would be indeterminate without other rules. The following expression
illustrates the problem:
x = 2 * 6 + 16 / 4
Strictly using the left-to-right evaluation of the expression,
the multiplication operation 2 * 6
is carried out first, which leaves a result of 12.
The addition operation 12 + 16
is then performed, which gives a result of 28.
The division operation 28 / 4
is then performed, which gives a result of 7.
Finally, the assignment operation x =
7 is handled, in which the number 7
is assigned to the variable x.
If you have some experience with operator precedence from another
language, you might already be questioning the evaluation of this
expression, and for good reason-it's wrong! The problem is that
using a simple left-to-right evaluation of expressions can yield
inconsistent results, depending on the order of the operators.
The solution to this problem lies in operator precedence,
which determines the order in which operators are evaluated. Every
Java operator has an associated precedence. Following is a list
of all the Java operators from highest to lowest precedence. In
this list of operators, all the operators in a particular row
have equal precedence. The precedence level of each row decreases
from top to bottom. This means that the []
operator has a higher precedence than the *
operator, but the same precedence as the ()
operator.
. | []
| ()
|
|
++ | --
| !
| ~
|
* | /
| %
| |
+
| -
|
|
|
<<
| >>
| >>>
|
|
<
| >
| <=
| >=
|
==
&
^
&&
||
?:
=
| !=
|
|
|
Evaluation of expressions still moves from left to right, but
only when dealing with operators that have the same precedence.
Otherwise, operators with a higher precedence are evaluated before
operators with a lower precedence. Knowing this, take another
look at the sample equation:
x = 2 * 6 + 16 / 4
Before using the left-to-right evaluation of the expression, first
look to see whether any of the operators have differing precedence.
Indeed they do! The multiplication (*)
and division (/) operators
both have the highest precedence, followed by the addition operator
(+), and then the assignment
operator (=). Because the
multiplication and division operators share the same precedence,
evaluate them from left to right. Doing this, you first perform
the multiplication operation 2 * 6
with the result of 12. You
then perform the division operation 16
/ 4, which results in 4.
After performing these two operations, the expression looks like
this:
x = 12 + 4;
Because the addition operator has a higher precedence than the
assignment operator, you perform the addition operation 12
+ 4 next, resulting in 16.
Finally, the assignment operation x =
16 is processed, resulting in the number 16
being assigned to the variable x.
As you can see, evaluating the expression using operator precedence
yields a completely different result.
Just to get the point across, take a look at another expression
that uses parentheses for grouping purposes:
x = 2 * (11 - 7);
Without the grouping parentheses, you would perform the multiplication
operation first and then the subtraction operation. However, referring
back to the precedence list, the ()
operator comes before all other operators. So the subtraction
operation 11 - 7 is performed
first, yielding 4 and the
following expression:
x = 2 * 4;
The rest of the expression is easily resolved with a multiplication
and an assignment to yield a result of 8
in the variable x.
Integer Operators
There are three types of operations that can be performed on integers:
unary, binary, and relational. Unary operators act on only single
integer numbers, and binary operators act on pairs of integer
numbers. Both unary and binary integer operators typically return
integer results. Relational operators, on the other hand, act
on two integer numbers but return a boolean result rather than
an integer.
Unary and binary integer operators typically return an int
type. For all operations involving the types byte,
short, and int,
the result is always an int.
The only exception to this rule is when one of the operands is
a long, in which case the
result of the operation is also of type long.
Unary Integer Operators
Unary integer operators act on a single integer. Table 7.1 lists
the unary integer operators.
Table 7.1. The unary integer operators.
Description | Operator
|
Increment | ++
|
Decrement | --
|
Negation | -
|
Bitwise complement | ~
|
The increment and decrement operators (++
and --) increase and decrease
integer variables by 1. Similar to their complements in C and
C++, these operators can be used in either prefix or postfix form.
A prefix operator takes effect before the evaluation of
the expression it is in; a postfix operator takes effect
after the expression has been evaluated. Prefix unary operators
are placed immediately before the variable; postfix unary operators
are placed immediately following the variable. Following are examples
of each type of operator:
y = ++x;
z = x--;
In the first example, x is
prefix incremented, which means that it is incremented
before being assigned to y.
In the second example, x
is postfix decremented, which means that it is decremented
after being assigned to z.
In the latter case, z is
assigned the value of x before
x is decremented. Listing
7.1 contains the IncDec program,
which uses both types of operators. Please note that the IncDec
program is actually implemented in the Java class IncDec.
This is a result of the object-oriented structure of Java, which
requires programs to be implemented as classes. When you see a
reference to a Java program, keep in mind that it is really
referring to a Java class.
Listing 7.1. The IncDec
class.
class IncDec {
public static void main (String args[]) {
int x = 8, y = 13;
System.out.println("x = " + x);
System.out.println("y = " + y);
System.out.println("++x = " + ++x);
System.out.println("y++ = " + y++);
System.out.println("x = " + x);
System.out.println("y = " + y);
}
}
The IncDec program produces the following results:
x = 8
y = 13
++x = 9
y++ = 13
x = 9
y = 14
The negation unary integer operator (-)
is used to change the sign of an integer value. This operator
is as simple as it sounds, as indicated by the following example:
x = 8;
y = -x;
In this example, x is assigned
the literal value 8 and then
is negated and assigned to y.
The resulting value of y
is -8. To see this code in
a real Java program, check out the Negation
program in Listing 7.2.
Listing 7.2. The Negation
class.
class Negation {
public static void main (String args[]) {
int x = 8;
System.out.println("x = " + x);
int y = -x;
System.out.println("y = " + y);
}
}
The last Java unary integer operator is the bitwise complement
operator (~), which performs
a bitwise negation of an integer value. Bitwise negation
means that each bit in the number is toggled. In other words,
all the binary 0s become 1s and all the binary 1s become 0s. Take
a look at an example very similar to the one for the negation
operator:
x = 8;
y = ~x;
In this example x is assigned
the literal value 8 again,
but it is bitwise complemented before being assigned to y.
What does this mean? Well, without getting into the details of
how integers are stored in memory, it means that all the bits
of the variable x are flipped,
yielding a decimal result of -9.
This result has to do with the fact that negative numbers are
stored in memory using a method known as two's complement
(see the following note). If you're having trouble believing any
of this, try it yourself with the BitwiseComplement
program shown in Listing 7.3.
Note |
Integer numbers are stored in memory as a series of binary bits that can each have a value of 0 or 1. A number is considered negative if the highest-order bit in the number is set to 1. Because a bitwise complement flips all the bits in a number-including the high-order bit-the sign of a number is reversed.
|
Listing 7.3. The BitwiseComplement
class.
class BitwiseComplement {
public static void main (String args[]) {
int x = 8;
System.out.println("x = " + x);
int y = ~x;
System.out.println("y = " + y);
}
}
Binary Integer Operators
Binary integer operators act on pairs of integers. Table 7.2 lists
the binary integer operators.
Table 7.2. The binary integer operators.
Description | Operator
|
Addition | +
|
Subtraction | -
|
Multiplication | *
|
Division | /
|
Modulus | %
|
Bitwise AND
| &
|
Bitwise OR
| |
|
Bitwise XOR
| ^
|
Left-shift | <<
|
Right-shift | >>
|
Zero-fill-right-shift | >>>
|
The addition, subtraction, multiplication, and division operators
(+, -,
*, and /)
all do what you expect them to. An important thing to note is
how the division operator works; because you are dealing with
integer operands, the division operator returns an integer divisor.
In cases where the division results in a remainder, the modulus
operator (%) can be used
to get the remainder value. Listing 7.4 contains the Arithmetic
program, which shows how the basic binary integer arithmetic operators
work.
Listing 7.4. The Arithmetic
class.
class Arithmetic {
public static void main (String args[]) {
int x = 17, y = 5;
System.out.println("x = " + x);
System.out.println("y = " + y);
System.out.println("x + y = " + (x + y));
System.out.println("x - y = " + (x - y));
System.out.println("x * y = " + (x * y));
System.out.println("x / y = " + (x / y));
System.out.println("x % y = " + (x % y));
}
}
The results of running the Arithmetic
program follow:
x = 17
y = 5
x + y = 22
x - y = 12
x * y = 85
x / y = 3
x % y = 2
These results shouldn't surprise you too much. Just notice that
the division operation x / y,
which boils down to 17 / 5,
yields the result 3. Also
notice that the modulus operation x %
y, which is resolved down to 17
% 5, ends with a result of 2
(the remainder of the integer division).
Mathematically, a division by zero results in an infinite result.
Because representing infinite numbers is a big problem for computers,
division or modulus operations by zero result in an error. To
be more specific, a runtime exception is thrown. You learn a lot
more about exceptions in Chapter 10, "Exception
Handling."
The bitwise AND, OR,
and XOR operators (&,
|, and ^)
all act on the individual bits of an integer. These operators
are sometimes useful when an integer is being used as a bit field.
An example of this is when an integer is used to represent a group
of binary flags. An int is
capable of representing up to 32 different flags because it is
stored in 32 bits. Listing 7.5 contains the program Bitwise,
which shows how to use the binary bitwise integer operators.
Note |
Java actually includes a class that provides specific support for storing binary flags. The class is called BitSet, and you learn about it in Chapter 13, "The Utilities Package."
|
Listing 7.5. The Bitwise
class.
class Bitwise {
public static void main (String args[]) {
int x = 5, y = 6;
System.out.println("x = " + x);
System.out.println("y = " + y);
System.out.println("x & y = " + (x & y));
System.out.println("x | y = " + (x | y));
System.out.println("x ^ y = " + (x ^ y));
}
}
The output of running Bitwise
follows:
x = 5
y = 6
x & y = 4
x | y = 7
x ^ y = 3
To understand this output, you must first understand the binary
equivalents of each decimal number. In Bitwise,
the variables x and y
are set to 5 and 6,
which correspond to the binary numbers 0101
and 0110. The bitwise AND
operation compares each bit of each number to see whether they
are the same. It then sets the resulting bit to 1
if both bits being compared are 1;
it sets the resulting bit to 0
otherwise. The result of the bitwise AND
operation on these two numbers is 0100
in binary, or decimal 4.
The same logic is used for both of the other operators, except
that the rules for comparing the bits are different. The bitwise
OR operator sets the resulting
bit to 1 if either of the
bits being compared is 1.
For these numbers, the result is 0111
binary, or 7 decimal. Finally,
the bitwise XOR operator
sets resulting bits to 1
if exactly one of the bits being compared is 1,
and 0 otherwise. For these
numbers, the result is 0011
binary, or 3 decimal.
The left-shift, right-shift, and zero-fill-right-shift operators
(<<, >>,
and >>>) shift the
individual bits of an integer by a specified integer amount. Following
are some examples of how these operators are used:
x << 3;
y >> 7;
z >>> 2;
In the first example, the individual bits of the integer variable
x are shifted to the left
three places. In the second example, the bits of y
are shifted to the right seven places. Finally, the third example
shows z being shifted to
the right two places, with zeros shifted into the two leftmost
places. To see the shift operators in a real program, check out
Shift in Listing 7.6.
Listing 7.6. The Shift
class.
class Shift {
public static void main (String args[]) {
int x = 7;
System.out.println("x = " + x);
System.out.println("x >> 2 = " + (x >> 2));
System.out.println("x << 1 = " + (x << 1));
System.out.println("x >>> 1 = " + (x >>> 1));
}
}
The output of Shift follows:
x = 7
x >> 2 = 1
x << 1 = 14
x >>> 1 = 3
The number being shifted in this case is the decimal 7,
which is represented in binary as 0111.
The first right-shift operation shifts the bits two places to
the right, resulting in the binary number 0001,
or decimal 1. The next operation,
a left-shift, shifts the bits one place to the left, resulting
in the binary number 1110,
or decimal 14. The last operation
is a zero-fill-right-shift, which shifts the bits one place to
the right, resulting in the binary number 0011,
or decimal 3. Pretty simple,
eh? And you probably thought it was difficult working with integers
at the bit level!
Based on these examples, you may be wondering what the difference
is between the right-shift (>>)
and zero-fill-right-shift (>>>)
operators. The right-shift operator appears to shift zeros into
the leftmost bits, just like the zero-fill-right-shift operator,
right? Well, when dealing with positive numbers, there is no difference
between the two operators-they both shift zeros into the upper
bits of a number. The difference arises when you start shifting
negative numbers. Remember that negative numbers have the high-order
bit set to 1. The right-shift
operator preserves the high-order bit and effectively shifts the
lower 31 bits to the right. This behavior yields results for negative
numbers similar to those for positive numbers. That is, -8
shifted right by one results in -4.
The zero-fill-right-shift operator, on the other hand, shifts
zeros into all the upper bits, including the high-order
bit. When this shifting is applied to negative numbers, the high-order
bit becomes 0 and the number
becomes positive.
Relational Integer Operators
The last group of integer operators is the relational operators,
which all operate on integers but return a type boolean.
Table 7.3 lists the relational integer operators.
Table 7.3. The relational integer operators.
Description | Operator
|
Less-than | <
|
Greater-than | >
|
Less-than-or-equal-to | <=
|
Greater-than-or-equal-to | >=
|
Equal-to | ==
|
Not-equal-to | !=
|
These operators all perform comparisons between integers. Listing
7.7 contains the Relational
program, which demonstrates the use of the relational operators
with integers.
Listing 7.7. The Relational
class.
class Relational {
public static void main (String args[]) {
int x = 7, y = 11, z = 11;
System.out.println("x = " + x);
System.out.println("y = " + y);
System.out.println("z = " + z);
System.out.println("x < y = " + (x < y));
System.out.println("x > z = " + (x > z));
System.out.println("y <= z = " + (y <= z));
System.out.println("x >= y = " + (x >= y));
System.out.println("y == z = " + (y == z));
System.out.println("x != y = " + (x != z));
}
}
The output of running Relational
follows:
x = 7
y = 11
z = 11
x < y = true
x > z = false
y <= z = true
x >= y = false
y == z = true
x != y = true
As you can see, the println()
method is smart enough to print boolean results correctly as true
and false.
Floating-Point Operators
Similar to integer operators, there are three types of operations
that can be performed on floating-point numbers: unary, binary,
and relational. Unary operators act only on single floating-point
numbers, and binary operators act on pairs of floating-point numbers.
Both unary and binary floating-point operators return floating-point
results. Relational operators, however, act on two floating-point
numbers but return a boolean result.
Unary and binary floating-point operators return a float
type if both operands are of type float.
If one or both of the operands is of type double,
however, the result of the operation is of type double.
Unary Floating-Point Operators
The unary floating point operators act on a single floating-point
number. Table 7.4 lists the unary floating-point operators.
Table 7.4. The unary floating-point operators.
Description | Operator
|
Increment | ++
|
Decrement | --
|
As you can see, the only two unary floating point operators are
the increment and decrement operators. These two operators respectively
add and subtract 1.0 from
their floating-point operand.
Binary Floating-Point Operators
The binary floating-point operators act on a pair of floating-point
numbers. Table 7.5 lists the binary floating-point operators.
Table 7.5. The binary floating-point operators.
Description | Operator
|
Addition | +
|
Subtraction | -
|
Multiplication | *
|
Division | /
|
Modulus | %
|
The binary floating-point operators consist of the four traditional
binary operations (+, -,
*, /),
along with the modulus operator (%).
You might be wondering how the modulus operator fits in here,
considering that its use as an integer operator relied on an integer
division. If you recall, the integer modulus operator returned
the remainder of an integer division of the two operands. But
a floating-point division never results in a remainder, so what
does a floating-point modulus do? The floating-point modulus operator
returns the floating-point equivalent of an integer division.
What this means is that the division is carried out with both
floating-point operands, but the resulting divisor is treated
as an integer, resulting in a floating-point remainder. Listing
7.8 contains the FloatMath
program, which shows how the floating-point modulus operator works
along with the other binary floating-point operators.
Listing 7.8. The FloatMath
class.
class FloatMath {
public static void main (String args[]) {
float x = 23.5F, y = 7.3F;
System.out.println("x = " + x);
System.out.println("y = " + y);
System.out.println("x + y = " + (x + y));
System.out.println("x - y = " + (x - y));
System.out.println("x * y = " + (x * y));
System.out.println("x / y = " + (x / y));
System.out.println("x % y = " + (x % y));
}
}
The output of FloatMath follows:
x = 23.5
y = 7.3
x + y = 30.8
x - y = 16.2
x * y = 171.55
x / y = 3.21918
x % y = 1.6
The first four operations no doubt performed as you expected,
taking the two floating-point operands and yielding a floating-point
result. The final modulus operation determined that 7.3
divides into 23.5 an integral
amount of 3 times, leaving
a remaining result of 1.6.
Relational Floating-Point Operators
The relational floating-point operators compare two floating-point
operands, leaving a boolean result. The floating-point relational
operators are the same as the integer relational operators listed
in Table 7.3, earlier in this chapter, except that they work on
floating-point numbers.
Boolean Operators
Boolean operators act on boolean
types and return a boolean result. The boolean operators are listed
in Table 7.6.
Table 7.6. The boolean operators.
Description | Operator
|
Evaluation AND
| &
|
Evaluation OR
| |
|
Evaluation XOR
| ^
|
Logical AND
| &&
|
Logical OR
| ||
|
Negation | !
|
Equal-to | ==
|
Not-equal-to | !=
|
Conditional | ?:
|
The evaluation operators (&,
|, and ^)
evaluate both sides of an expression before determining the result.
The logical operators (&&
and ||) avoid the right-side
evaluation of the expression if it is not needed. To better understand
the difference between these operators, take a look at the following
two expressions:
boolean result = isValid & (Count > 10);
boolean result = isValid && (Count > 10);
The first expression uses the evaluation AND
operator (&) to make
an assignment. In this case, both sides of the expression are
always evaluated, regardless of the values of the variables involved.
In the second example, the logical AND
operator (&&) is
used. This time, the isValid
boolean value is first checked. If it is false,
the right side of the expression is ignored and the assignment
is made. This operator is more efficient because a false
value on the left side of the expression provides enough information
to determine the false outcome.
Although the logical operators are more efficient than the evaluation
operators, there still may be times when you want to use the evaluation
operators to ensure that the entire expression is evaluated. The
following code shows how the evaluation AND
operator is necessary for the complete evaluation of an expression:
while ((++x < 10) && (++y < 15)) {
System.out.println(x);
System.out.println(y);
}
In this example, the second expression (++y
< 15) is evaluated after the last pass through
the loop because of the evaluation AND
operator. If the logical AND
operator had been used, the second expression would not have been
evaluated and y would not
have been incremented after the last time around.
The three boolean operators-negation, equal-to, and not-equal-to
(!, ==,
and !=)-perform exactly as
you might expect. The negation operator toggles the value of a
boolean from false to true
or from true to false,
depending on the original value. The equal-to operator simply
determines whether two boolean values are equal (both true
or both false). Similarly,
the not-equal-to operator determines whether two boolean operands
are unequal.
The conditional boolean operator (?:)
is the most unique of the boolean operators and is worth a closer
look. This operator also is known as the ternary operator
because it takes three items: a condition and two expressions.
The syntax for the conditional operator follows:
Condition ? Expression1 : Expression2
The Condition, which
itself is a boolean, is first evaluated to determine whether it
is true or false.
If Condition evaluates
to a true result, Expression1
is evaluated. If Condition
ends up being false, Expression2
is evaluated. To get a better feel for the conditional operator,
check out the Conditional
program in Listing 7.9.
Listing 7.9. The Conditional
class.
class Conditional {
public static void main (String args[]) {
int x = 0;
boolean isEven = false;
System.out.println("x = " + x);
x = isEven ? 4 : 7;
System.out.println("x = " + x);
}
}
The results of the Conditional
program follow:
x = 0
x = 7
The integer variable x is
first assigned a value of 0.
The boolean variable isEven
is assigned a value of false.
Using the conditional operator, the value of isEven
is checked. Because it is false,
the second expression of the conditional is used, which results
in the value 7 being assigned
to x.
String Operators
Just as integers, floating-point numbers, and booleans can, strings
can be manipulated with operators. Actually, there is only one
string operator: the concatenation operator (+).
The concatenation operator for strings works very similarly to
the addition operator for numbers-it adds strings together. The
concatenation operator is demonstrated in the Concatenation
program shown in Listing 7.10.
Listing 7.10. The Concatenation
class.
class Concatenation {
public static void main (String args[]) {
String firstHalf = "What " + "did ";
String secondHalf = "you " + "say?";
System.out.println(firstHalf + secondHalf);
}
}
The output of Concatenation
follows:
What did you say?
In the Concatenation program,
literal strings are concatenated to make assignments to the two
string variables, firstHalf
and secondHalf, at time of
creation. The two string variables are then concatenated within
the call to the println()method.
Assignment Operators
One final group of operators you haven't seen yet is the assignment
operators. Assignment operators actually work with all the fundamental
data types. Table 7.7 lists the assignment operators.
Table 7.7. The assignment operators.
Description | Operator
|
Simple | =
|
Addition | +=
|
Subtraction | -=
|
Multiplication | *=
|
Division | /=
|
Modulus | %=
|
AND |
&=
|
OR |
|=
|
XOR |
^=
|
With the exception of the simple assignment operator (=),
the assignment operators function exactly like their nonassignment
counterparts, except that the resulting value is stored in the
operand on the left side of the expression. Take a look at the
following examples:
x += 6;
x *= (y - 3);
In the first example, x and
6 are added and the result
stored in x. In the second
example, 3 is subtracted
from y and the result multiplied
by x. The final result is
then stored in x.
Although performing operations on data is very useful, it's time
to move on to the issue of program flow control. The flow of your
programs is dictated by two different types of constructs: branches
and loops. Branches enable you to selectively execute one
part of a program instead of another. Loops, on the other
hand, provide a means to repeat certain parts of a program. Together,
branches and loops provide you with a powerful means to control
the logic and execution of your code.
Branches
Without branches or loops, Java code executes in a sequential
fashion, as shown in Figure 7.1.
Figure 7.1: A program executing sequentially.
In Figure 7.1, each statement is executed sequentially. But what
if you don't always want every single statement executed? Then
you use a branch. Figure 7.2 shows how a conditional branch gives
the flow of your code more options.
Figure 7.2: A program executing with a branch.
By adding a branch, you give the code two optional routes to take,
based on the result of the conditional expression. The concept
of branches might seem trivial, but it would be difficult if not
impossible to write useful programs without them. Java supports
two types of branches: if-else
branches and switch branches.
if-else Branches
The if-else branch is the
most commonly used branch in Java programming. It is used to select
conditionally one of two possible outcomes. The syntax for the
if-else statement follows:
if (Condition)
Statement1
else
Statement2
If the boolean Condition
evaluates to true, Statement1
is executed. Likewise, if Condition
evaluates to false, Statement2
is executed. The following example shows how to use an if-else
statement:
if (isTired)
timeToEat = true;
else
timeToEat = false;
If the boolean variable isTired
is true, the first statement
is executed and timeToEat
is set to true. Otherwise,
the second statement is executed and timeToEat
is set to false. You may
have noticed that the if-else
branch works in a manner very similar to the conditional operator
(?:) described earlier in
this chapter. In fact, you can think of the if-else
branch as an expanded version of the conditional operator. One
significant difference between the two is that you can include
compound statements in an if-else
branch, which you cannot do with the conditional operator.
Note |
Compound statements are blocks of code surrounded by curly braces {} that appear as a single, or simple, statement to an outer block of code.
|
If you have only a single statement that you want to execute conditionally,
you can leave off the else
part of the branch, as shown in the following example:
if (isThirsty)
pourADrink = true;
On the other hand, if you need more than two conditional outcomes,
you can string together a series of if-else
branches to get the desired effect. The following example shows
multiple if-else branches
used to switch between different outcomes:
if (x == 0)
y = 5;
else if (x == 2)
y = 25;
else if (x >= 3)
y = 125;
In this example, three different comparisons are made, each with
its own statement executed on a true
conditional result. Notice, however, that subsequent if-else
branches are in effect nested within the previous branch. This
arrangement ensures that at most one statement is executed.
The last important topic to cover in regard to if-else
branches is compound statements. As mentioned in the preceding
note, a compound statement is a block of code surrounded by curly
braces that appears to an outer block as a single statement. Following
is an example of a compound statement used with an if
branch:
if (performCalc) {
x += y * 5;
y -= 10;
z = (x - 3) / y;
}
Sometimes, when nesting if-else
branches, it is necessary to use curly braces to distinguish which
statements go with which branch. The following example illustrates
the problem:
if (x != 0)
if (y < 10)
z = 5;
else
z = 7;
In this example, the style of indentation indicates that the else
branch belongs to the first (outer) if.
However, because there was no grouping specified, the Java compiler
assumes that the else goes
with the inner if. To get
the desired results, you must modify the code as follows:
if (x != 0) {
if (y < 10)
z = 5;
}
else
z = 7;
The addition of the curly braces tells the compiler that the inner
if is part of a compound
statement; more importantly, it completely hides the else
branch from the inner if.
Based on what you learned from the discussion of blocks and scope
in the last chapter, you can see that code within the inner if
has no way of accessing code outside its scope, including the
else branch.
Listing 7.11 contains the source code for the IfElseName
class, which uses a lot of what you've learned so far.
Listing 7.11. The IfElseName
class.
class IfElseName {
public static void main (String args[]) {
char firstInitial = (char)-1;
System.out.println("Enter your first initial:");
try {
firstInitial = (char)System.in.read();
}
catch (Exception e) {
System.out.println("Error: " + e.toString());
}
if (firstInitial == -1)
System.out.println("Now what kind of name is that?");
else if (firstInitial == 'j')
System.out.println("Your name must be Jules!");
else if (firstInitial == 'v')
System.out.println("Your name must be Vincent!");
else if (firstInitial == 'z')
System.out.println("Your name must be Zed!");
else
System.out.println("I can't figure out your name!");
}
When typing the letter v
in response to the input message, IfElseName
yields the following results:
Your name must be Vincent!
The first thing in IfElseName
you probably are wondering about is the read()
method. The read() method
simply reads a character from the standard input stream (System.in),
which is typically the keyboard. Notice that a cast is used because
read() returns an int
type. Once the input character has been successfully retrieved,
a succession of if-else branches
is used to determine the proper output. If there are no matches,
the final else branch is
executed, which notifies users that their names could not be determined.
Notice that the value of read()
is checked to see whether it is equal to -1. The read()
method returns -1 if it has reached the end of the input stream.
Note |
You may have noticed that the call to the read() method in IfElseName is enclosed within a try-catch clause. The try-catch clause is part of Java's support for exception handling and is used in this case to trap errors encountered while reading input from the user. You'll learn more about exceptions and the try-catch clause in Chapter 10, "Exception Handling."
|
switch Branches
Similar to the if-else branch,
the switch branch is specifically
designed to conditionally switch among multiple outcomes. The
syntax for the switch statement
follows:
switch (Expression) {
case Constant1:
StatementList1
case Constant2:
StatementList2
default:
DefaultStatementList
}
The switch branch evaluates
and compares Expression
to all the case constants
and branches the program's execution to the matching case
statement list. If no case
constants match Expression,
the program branches to the DefaultStatementList,
if one has been supplied (the DefaultStatementList
is optional). You might be wondering what a statement list is.
A statement list is simply a series, or list, of statements.
Unlike the if-else branch,
which directs program flow to a simple or compound statement,
the switch branch directs
the flow to a list of statements.
When the program execution moves into a case
statement list, it continues from there in a sequential manner.
To better understand this, take a look at Listing 7.12, which
contains a switch version
of the name program developed earlier with if-else
branches.
Listing 7.12. The SwitchName1
class.
class SwitchName1 {
public static void main (String args[]) {
char firstInitial = (char)-1;
System.out.println("Enter your first initial:");
try {
firstInitial = (char)System.in.read();
}
catch (Exception e) {
System.out.println("Error: " + e.toString());
}
switch(firstInitial) {
case (char)-1:
System.out.println("Now what kind of name is that?");
case 'j':
System.out.println("Your name must be Jules!");
case 'v':
System.out.println("Your name must be Vincent!");
case 'z':
System.out.println("Your name must be Zed!");
default:
System.out.println("I can't figure out your name!");
}
}
}
When typing the letter v
in response to the input message, SwitchName1
produces the following results:
Your name must be Vincent!
Your name must be Zed!
I can't figure out your name!
Hey, what's going on here? That output definitely does not look
right. The problem lies in the way the switch
branch controls program flow. The switch
branch matched the v
entered with the correct case
statement, as shown in the first string printed. However, the
program continued executing all the case
statements from that point onward, which is not what you
wanted. The solution to the problem lies in the break
statement. The break statement
forces a program to break out of the block of code it is currently
executing. Check out the new version of the program in Listing
7.13, which has break statements
added where appropriate.
Listing 7.13. The SwitchName2
class.
class SwitchName2 {
public static void main (String args[]) {
char firstInitial = (char)-1;
System.out.println("Enter your first initial:");
try {
firstInitial = (char)System.in.read();
}
catch (Exception e) {
System.out.println("Error: " + e.toString());
}
switch(firstInitial) {
case (char)-1:
System.out.println("Now what kind of name is that?");
break;
case 'j':
System.out.println("Your name must be Jules!");
break;
case 'v':
System.out.println("Your name must be Vincent!");
break;
case 'z':
System.out.println("Your name must be Zed!");
break;
default:
System.out.println("I can't figure out your name!");
}
}
}
When you run SwitchName2
and enter v, you get
the following output:
Your name must be Vincent!
That's a lot better! You can see that placing break
statements after each case
statement kept the program from falling through to the next case
statements. Although you will use break
statements in this manner the majority of the time, there may
still be some situations where you will want a case
statement to fall through to the next one.
Loops
When it comes to program flow, branches really tell only half
the story; loops tell the other half. Put simply, loops
enable you to execute code repeatedly. There are three types of
loops in Java: for loops,
while loops, and do-while
loops.
Just as branches alter the sequential flow of programs, so do
loops. Figure 7.3 shows how a loop alters the sequential flow
of a Java program.
Figure 7.3: A program executing with a loop.
for Loops
The for loop provides a means
to repeat a section of code a designated number of times. The
for loop is structured so
that a section of code is repeated until some limit has been reached.
The syntax for the for statement
follows:
for (InitializationExpression; LoopCondition; StepExpression)
Statement
The for loop repeats the
Statement the number
of times that is determined by the InitializationExpression,
the LoopCondition,
and the StepExpression.
The InitializationExpression
is used to initialize a loop control variable. The LoopCondition
compares the loop control variable to some limit value. Finally,
the StepExpression
specifies how the loop control variable should be modified before
the next iteration of the loop. The following example shows how
a for loop can be used to
print the numbers from 1 to 10:
for (int i = 1; i < 11; i++)
System.out.println(i);
First, i is declared as an
integer. The fact that i
is declared within the body of the for
loop might look strange to you at this point. Don't despair-this
is completely legal. i is
initialized to 1 in the InitializationExpression
part of the for loop. Next,
the conditional expression i < 11
is evaluated to see whether the loop should continue. At this
point, i is still equal to
1, so LoopCondition
evaluates to true and the
Statement is executed
(the value of i is printed
to standard output). i is
then incremented in the StepExpression
part of the for loop, and
the process repeats with the evaluation of LoopCondition
again. This continues until LoopCondition
evaluates to false, which
is when x equals 11
(10 iterations later).
Listing 7.14 shows the ForCount
program, which shows how to use a for
loop to count a user-entered amount of numbers.
Listing 7.14. The ForCount
class.
class ForCount {
public static void main (String args[]) {
char input = (char)-1;
int numToCount;
System.out.println("Enter a number to count to between 0 and 10:");
try {
input = (char)System.in.read();
}
catch (Exception e) {
System.out.println("Error: " + e.toString());
}
numToCount = Character.digit(input, 10);
if ((numToCount > 0) && (numToCount < 10)) {
for (int i = 1; i <= numToCount; i++)
System.out.println(i);
}
else
System.out.println("That number was not between 0 and 10!");
}
}
When the ForCount program
is run and the number 4 is
entered, the following output results:
1
2
3
4
ForCount first prompts the
user to enter a number between 0 and 10. A character is read from
the keyboard using the read()
method and the result stored in the input
character variable. The static digit
method of the Character class
then is used to convert the character to its base 10 integer representation.
This value is stored in the numToCount
integer variable. numToCount
is then checked to make sure that it is in the range 0 to 10.
If so, a for loop is executed
that counts from 1 to numToCount,
printing each number along the way. If numToCount
is outside the valid range, an error message is printed.
Before you move on, there is one small problem with ForCount
that you may not have noticed. Run it and try typing in a number
greater than 9. What happened to the error message? The problem
is that ForCount grabs only
the first character it sees from the input. So if you type 10,
ForCount just gets the 1
and thinks everything is fine. You don't have to worry about fixing
this problem right now because it will be resolved when you learn
more about input and output in Chapter 14,
"The I/O Package."
while Loops
Like the for loop, the while
loop has a loop condition that controls the execution of the loop
statement. Unlike the for
loop, however, the while
loop has no initialization or step expressions. The syntax for
the while statement follows:
while (LoopCondition)
Statement
If the boolean LoopCondition
evaluates to true, the Statement
is executed and the process starts over. It is important to understand
that the while loop has no
step expression as the for
loop does. This means that the LoopCondition
must somehow be affected by code in the Statement
or the loop will infinitely repeat, which is a bad thing. It is
bad because an infinite loop causes a program to never exit, which
hogs processor time and can ultimately hang the system.
Another important thing to notice about the while
loop is that its LoopCondition
occurs before the body of the loop Statement.
This means that if the LoopCondition
initially evaluates to false,
the Statement is never
executed. Although this may seem trivial, it is in fact the only
thing that differentiates the while
loop from the do-while loop,
which is discussed in the next section.
To better understand how the while
loop works, take a look at Listing 7.15, which shows how a counting
program works using a while
loop.
Listing 7.15. The WhileCount
class.
class WhileCount {
public static void main (String args[]) {
char input = (char)-1;
int numToCount;
System.out.println("Enter a number to count to between 0 and 10:");
try {
input = (char)System.in.read();
}
catch (Exception e) {
System.out.println("Error: " + e.toString());
}
numToCount = Character.digit(input, 10);
if ((numToCount > 0) && (numToCount < 10)) {
int i = 1;
while (i <= numToCount) {
System.out.println(i);
i++;
}
}
else
System.out.println("That number was not between 0 and 10!");
}
}
Arguably, WhileCount doesn't
demonstrate the best usage of a while
loop. Loops that involve counting should almost always be implemented
with for loops. However,
seeing how a while loop can
be made to imitate a for
loop can give you insight into the structural differences between
the two types of loops.
Because while loops don't
have any type of initialization expression, you first have to
declare and initialize the variable i
to 1. Next, the loop condition
for the while loop is established
as i <= numToCount. Inside
the compound while statement,
you can see a call to the println()
method, which outputs the value of i.
Finally, i is incremented
and program execution resumes back at the while
loop condition.
do-while Loops
The do-while loop is very
similar to the while loop,
as you can see in the following syntax:
do
Statement
while (LoopCondition);
The major difference between the do-while
loop and the while loop is
that, in a do-while loop,
the LoopCondition
is evaluated after the Statement
is executed. This difference is important because there may be
times when you want the Statement
code to be executed at least once, regardless of the LoopCondition.
The Statement is executed
initially, and from then on it is executed as long as the LoopCondition
evaluates to true. As with
the while loop, you must
be careful with the do-while
loop to avoid creating an infinite loop. An infinite loop occurs
when the LoopCondition
remains true indefinitely.
The following example shows a very obvious infinite do-while
loop:
do
System.out.println("I'm stuck!");
while (true);
Because the LoopCondition
is always true, the message
I'm Stuck! is printed forever,
or at least until you press Ctrl+C and break out of the program.
break
and continue
Statements
You've already seen how the break
statement works with the switch
branch. The break statement
is also useful when dealing with loops. You can use the break
statement to jump out of a loop and effectively bypass the loop
condition. Listing 7.16 shows how the break
statement can help you out of the infinite loop problem shown
earlier.
Listing 7.16. The BreakLoop
class.
class BreakLoop {
public static void main (String args[]) {
int i = 0;
do {
System.out.println("I'm stuck!");
i++;
if (i > 100)
break;
}
while (true);
}
}
In BreakLoop, a seemingly
infinite do-while loop is
created by setting the loop condition to true.
However, the break statement
is used to exit the loop when i
is incremented past 100.
Another useful statement that works similarly to the break
statement is the continue
statement. Unlike break,
the continue statement is
useful only when working with loops; it has no real application
to the switch branch. The
continue statement works
like the break statement
in that it jumps out of the current iteration of a loop. The difference
with continue is that program
execution is restored to the test condition of the loop. Remember
that break jumps completely
out of a loop. Use break
when you want to jump out and terminate a loop; use continue
when you want to jump immediately to the next iteration of the
loop. The following example shows the difference between the break
and continue statements:
int i = 0;
while (i++ < 100) {
System.out.println("Looping with i.");
break;
System.out.println("Please don't print me!");
}
int j = 0;
while (j++ < 100) {
System.out.println("Looping with j!");
continue;
System.out.println("Please don't print me!");
}
In this example, the first loop breaks out because of the break
statement after printing the looping message once. Note that the
second message is never printed because the break
statement occurs before we get to it. Contrast this with the second
loop, which prints the looping message 100 times. The reason for
this is that the continue
statement allows the loop to continue its iterations. In this
case, the continue statement
serves only to skip over the second message, which still isn't
printed.
This chapter covered a lot of territory. You started off by learning
about expressions and then moved right into operators, learning
how they work and how they affect each data type. You won't regret
the time spent working with operators in this chapter-they are
at the core of almost every mathematical or logical Java expression.
From operators, you moved on to control structures, learning about
the various types of branches and loops. Branches and loops provide
the means to alter the flow of Java programs and are just as important
as operators in Java programming.
With the concepts presented in this chapter firmly set in your
mind, you are ready to dig a little deeper into Java. Next stop:
object-oriented programming with classes, packages, and interfaces!
Contact
reference@developer.com with questions or comments.
Copyright 1998
EarthWeb Inc., All rights reserved.
PLEASE READ THE ACCEPTABLE USAGE STATEMENT.
Copyright 1998 Macmillan Computer Publishing. All rights reserved.
|