Previous Page toc Index Next Page

Chapter 13

Expressions, operators, and control structures

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 of 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, but also learn what it takes to write some more interesting programs.

Expressions and Operators

Once you have created 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. This is the standard technique for breaking down and understanding expressions in Java, as well as most other programming languages. This right-to-left evaluation of expressions isn’t just a technique for your own understanding of expressions—it’s how the compiler itself analyzes expressions to generate code.

Operator Precedence

Even with the compiler analyzing expressions right to left, there still are many times when the result of an expression would be indeterminate without any other rules. The following expression illustrates the problem:

x = 2 * 5 + 12 / 4

Strictly using the right-to-left evaluation of the expression, the division operation 12 / 4 is carried out first, which leaves a result of 3. The addition operation 5 + 3 is then performed, which gives you a result of 8. The multiplication operation 2 * 8 is then performed, which gives you a result of 16. Finally, the assignment operation x = 16 is handled, in which case the number 16 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 right-to-left evaluation of expressions can yield inconsistentresults, 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 listing of all the Java operators from highest to lowest precedence:

. [] ()

++ -- ! ~

* / %

+ -

<< >> >>>

< > <= >=

== !=

&

^

&&

||

?:

=

In this list of operators, all of 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.

Expression evaluation still moves from right to left, 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 a look at the same equation again:

x = 2 * 5 + 12 / 4

Before using the right-to-left evaluation of the expression, first look to see if 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 theassignment operator (=). Because the multiplication and division operators share the same precedence, evaluate them from right to left. Doing this, you perform the division operation 12 / 4 first, resulting in 3. You then perform the multiplication operation 2 * 5, which results in 10. After performing these two operations, the expression looks like this:

x = 10 + 3;

Because the addition operator has a higher precedence than the assignment operator, you perform the addition 10 + 3 next, resulting in 13. Finally, the assignment operation x = 13 is processed, resulting in the number 13 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 first and then the subtraction. However, referring back to the precedence list, the () operator comes before all other operators. So, the subtraction 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 only on single integer numbers, and binary operators act on pairs of integer numbers. Both unary and binary integer operators 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 if one of the operands is a long, in which case the result of the operation also will be of type long.

Unary

Unary integer operators act on a single integer. Table 13.1 lists the unary integer operators.

Table 13.1. The unary integer operators.

Description Operator
Increment ++
.Decrement --
Negation -
Bitwise complement ~

The increment and decrement operators (++ and --) increase and decrease integer variables by one. Similar to their complements in C and C++, these operators can be used in either prefix or postfix form. A prefix operator takes effect prior to the evaluation of the expression it is in, and a postfix operator takes effect after the expression has been evaluated. Prefix unary operators are placed immediately before the variable and postfix unary operators are placed immediately following the variable. Following is an example 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 prior to x being decremented. Listing 13.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. So, when you see a reference to a Java program, keep in mind that it is really referring to a Java 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 13.2.

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 of the binary zeros become ones and all the binary ones become zeros. 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 of 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 13.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.
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

Binary integer operators act on pairs of integers. Table 13.2 lists the binary integer operators.

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

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 up with a result of 2, which is 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’ll learn a lot more about exceptions in Chapter 16, “Exception Handling.”

The bitwise AND, OR, and XOR operators (&, |, and ^) all act on the individual bits of an integer. These operators sometimes are 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 13.5 contains the program Bitwise, which shows how to use the binary bitwise integer operators.

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 if they are the same. It then sets the resulting bit to 1 if both bits being compared are 1, and 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. The 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 13.6.

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. Finally, the last operation is a zero-fill right shift, which shifts the bits 1 place to the right, resulting in the binary number 0011, or decimal 3. Pretty simple, huh? 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 will result 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

The last group of integer operators is the relational operators, which all operate on integers but return a type boolean. Table 13.3 lists the relational integer operators.

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

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 onfloating-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

The unary floating point operators act on a single floating-point number. Table 13.4 lists the unary floating-point operators.

Table 13.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

The binary floating-point operators act on a pair of floating-point numbers. Table 13.5 lists the binary floating-point operators.

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

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

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 13.3, 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 13.6.

Table 13.6. The Boolean operators.

D="I228" NAME="I228">
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 always are 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 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, 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 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 into 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 13.9.

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

Along with integers, floating-point numbers, and Booleans, strings also 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 13.10.

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, upon creation. The two string variables are then concatenated within the call to the println method.

Assignment Operators

One final group of operators that you haven’t seen yet is the assignment operators. Assignment operators actually work with all of the fundamental data types. Table 13.7 lists the assignment operators.

Table 13.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.

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

In Figure 13.1, each statement is executed sequentially. What if you don’t always want every single statement executed? Then you use a branch. Figure 13.2 shows how a conditional branch gives the flow of your code more options.

Figure FIGURE 13.1.

A program executing sequentially.

Figure FIGURE 13.2.

A program executing with a branch.

By adding a branch, you’ve given 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

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 the Condition evaluates to false, Statement2 is executed. The following example should make it a little more clear:

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 might have noticed that the if-else branch works very similarly to the conditional operator (?:) you saw earlier. 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.

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 need 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 that is executed upon a true conditional result. Notice, however, that subsequent if-else branches are in effect nested within the prior branch. This 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 earlier, a compound statement is a block of code surrounded by curly braces that appears to an outer block as a single statement. The 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 need to 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, and 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 13.11 contains the source code for the IfElseName class, which uses a lot of what you’ve learned so far.

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 are 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 read is checked to see if 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 16, “Exception Handling.”

Switch

Similar to the if-else branch, the switch branch specifically is 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 of 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 13.12, which contains a switch version of the name program that you developed earlier with if-else branches.

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 of 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 13.13, with break statements added where appropriate.

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 might 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 only tell 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 13.3 shows how a loop alters the sequential flow of a Java program.

Figure FIGURE 13.3.

A program executing with a loop.

for

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 illustrates how a for loop can be used to print the numbers from one to ten:

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 expressioni < 11 is evaluated to see if 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 13.14 shows the ForCount program, which shows how to use a for loop to count a user-entered amount of numbers.

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 zero and ten. 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 zero to ten. If so, a for loop is executed that counts from 1 to numToCount, printing each number along the way. If numToCount is outside of the valid range, an error message is printed.

Before you move on, there is one small problem with ForCount that you might not have noticed. Run it and try typing in a number greater than nine. What happened to the error message? The problem is that ForCount is grabbing only the first character it sees from the input. So if you type in 300, it will just get the 3 and think everything is fine. You don’t need to worry about fixing this problem right now, as it will be resolved when you learn more about input and output in Chapter 20, “The I/O Package.”

while

Like the for loop, the while loop has a loop condition that controls the execution of the loop statement. Unlike the for loop, 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 there is no step expression, as in a for loop. 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 never will be executed. This might seem trivial, but 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 13.15, which shows how a counting program works using a while loop.

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 almost should 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.

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

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 the LoopCondition is evaluated after the Statement is executed. This difference is important because there might be times when you want the Statement code to be executed at least once, regardless of the LoopCondition. The syntax for the do-while loop follows:

The Statement is executed initially, and from then on it is executed as long as the LoopCondition evaluates to true. Like 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 illustrates 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 hit Ctrl+C and break out of the program.

break and continue

You’ve already seen how the break statement works in regard to 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 13.16 shows how the break statement can help you out of the infinite loop problem shown earlier.

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 only useful when working with loops and 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, break jumps completely out of a loop. Use break when you want to jump out and terminate a loop, and use continue when you want to jump immediately to the next iteration of the loop.

Summary

A lot of territory has been covered in this chapter. 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 the whole realm of 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!


Previous Page toc Index Next Page