TOC
BACK
FORWARD
HOME

Java 1.1 Unleashed

- 4 -
Expressions, Operators, and Control Structures

by Michael Morrison

IN THIS CHAPTER

  • Expressions and Operators
  • Control Structures


In Chapter 3, "Java Language Fundamentals," you learned about some of 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.

Expressions and Operators

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 operation and an assignment operation 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 4.1 lists the unary integer operators.

Table 4.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 4.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 4.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 4.2.

Listing 4.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 4.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 4.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 4.2 lists the binary integer operators.

Table 4.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 4.4 contains the Arithmetic program, which shows how the basic binary integer arithmetic operators work.

Listing 4.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 operations in an error. To be more specific, a runtime exception is thrown. You learn a lot more about exceptions in Chapter 7, "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 4.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 11, "The Utilities Package."

Listing 4.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 4.6.

Listing 4.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 4.3 lists the relational integer operators.

Table 4.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 4.7 contains the Relational program, which demonstrates the use of the relational operators with integers.

Listing 4.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 are 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 4.4 lists the unary floating-point operators.

Table 4.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 4.5 lists the binary floating-point operators.

Table 4.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 4.8 contains the FloatMath program, which shows how the floating-point modulus operator works along with the other binary floating-point operators.

Listing 4.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 4.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 4.6.

Table 4.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 4.9.

Listing 4.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 Operator

Just as integers, floating-point numbers, and booleans can be, 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 4.10.

Listing 4.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 4.7 lists the assignment operators.

Table 4.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 is multiplied by x. The final result is then stored in x.

Control Structures

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

Figure 4.1.

A program executing sequentially.

In Figure 4.1, each statement is executed sequentially. But what if you don't always want every single statement executed? Then you use a branch. Figure 4.2 shows how a conditional branch gives the flow of your code more options.

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.

Figure 4.2.

A program executing with a branch.

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 Chapter 3, "Java Language Fundamentals," you can see that code within the inner if has no way of accessing code outside its scope, including the else branch.

Listing 4.11 contains the source code for the IfElseName class, which uses a lot of what you've learned so far.

Listing 4.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 7, "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 4.12, which contains a switch version of the name program developed earlier with if-else branches.

Listing 4.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 4.13, which has break statements added where appropriate.

Listing 4.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 of 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 4.3 shows how a loop alters the sequential flow of a Java program.

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 lines 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);

Figure 4.3.

A program executing with a loop.

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 (ten iterations later).

Listing 4.14 shows the ForCount program, which shows how to use a for loop to count a user-entered amount of numbers.

Listing 4.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 is 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 12, "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. This 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 4.15, which shows how a counting program works using a while loop.

Listing 4.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 4.16 shows how the break statement can help you out of the infinite loop problem shown earlier.

Listing 4.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.

Summary

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!

TOCBACKFORWARDHOME


©Copyright, Macmillan Computer Publishing. All rights reserved.