Chapter 5. Decisions

CHAPTER GOALS

  • To be able to implement decisions using if statements

  • To effectively group statements into blocks

  • To learn how to compare integers, floating-point numbers, strings, and objects

  • To correctly order decisions in multiple branches and nested branches

  • To program conditions using Boolean operators and variables

    T To be able to design tests that cover all parts of a program

The programs we have seen so far were able to do fast computations and render graphs, but they were very inflexible. Except for variations in the input, they worked the same way with every program run. One of the essential features of nontrivial computer programs is their ability to make decisions and to carry out different actions, depending on the nature of the inputs. The goal of this chapter is to learn how to program simple and complex decisions.

The if Statement

Computer programs often need to make decisions, taking different actions depending on a condition.

Consider the bank account class of Chapter 3. The withdraw method allows you to withdraw as much money from the account as you like. The balance just moves ever further into the negatives. That is not a realistic model for a bank account. Let's implement the withdraw method so that you cannot withdraw more money than you have in the account. That is, the withdraw method must make a decision: whether to allow the withdrawal or not.

The if statement is used to implement a decision. The if statement has two parts: a condition and a body. If the condition is true, the body of the statement is executed. The body of the if statement consists of a statement:

if (amount <= balance) // Condition
balance = balance - amount; // Body

Note

The if statement lets a program carry out different actions depending on a condition.

The assignment statement is carried out only when the amount to be withdrawn is less than or equal to the balance (see Figure 1).

Let us make the withdraw method of the BankAccount class even more realistic. Most banks not only disallow withdrawals that exceed your account balance; they also charge you a penalty for every attempt to do so.

This operation can't be programmed simply by providing two complementary if statements, such as:

if (amount <= balance)
   balance = balance - amount;
if (amount > balance) // Use if/else instead
   balance = balance - OVERDRAFT_PENALTY;
Flowchart for an if Statement

Figure 5.1. Flowchart for an if Statement

Flowchart for an if/else Statement

Figure 5.2. Flowchart for an if/else Statement

There are two problems with this approach. First, if you need to modify the condition amount <= balance for some reason, you must remember to update the condition amount > balance as well. If you do not, the logic of the program will no longer be correct. More importantly, if you modify the value of balance in the body of the first if statement (as in this example), then the second condition uses the new value.

To implement a choice between alternatives, use the if/else statement:

if (amount <= balance)
   balance = balance - amount;
else
   balance = balance - OVERDRAFT_PENALTY;

Now there is only one condition. If it is satisfied, the first statement is executed. Otherwise, the second is executed. The flowchart in Figure 2 gives a graphical representation of the branching behavior.

Quite often, however, the body of the if statement consists of multiple statements that must be executed in sequence whenever the condition is true. These statements must be grouped together to form a blockstatement by enclosing them in braces { }. Here is an example.

if (amount <= balance)
{
   double newBalance = balance - amount;
   balance = newBalance;
}

Note

A block statement groups several statements together.

In general, the body of an if statement must be a block statement, a simple statement, such as

balance = balance - amount;

or a compound statement (another if statement or a loop—see Chapter 6). The else alternative also must be a statement—that is, a simple statement, a compound statement, or a block statement.

The if Statement

2. What is logically wrong with the statement

if (amount <= balance)
   newBalance = balance - amount; balance = newBalance;

and how do you fix it?

Comparing Values

Relational Operators

A relational operator tests the relationship between two values. An example is the <= operator that we used in the test

if (amount <= balance)

Note

Relational operators compare values. The == operator tests for equality.

Java has six relational operators:

Java

Math Notation

Description

>

>

Greater than

>=

Greater than or equal

<

<

Less than

<=

Less than or equal

==

=

Equal

!=

Not equal

As you can see, only two relational operators (> and <) look as you would expect from the mathematical notation. Computer keyboards do not have keys for ≥, ≤, or ≠, but the >=, <=, and != operators are easy to remember because they look similar.

The == operator is initially confusing to most newcomers to Java. In Java, the = symbol already has a meaning, namely assignment. The == operator denotes equality testing:

a = 5; // Assign 5 to a
if (a == 5) ... // Test whether a equals 5

You will have to remember to use == for equality testing, and to use = for assignment.

The relational operators have a lower precedence than the arithmetic operators. That means, you can write arithmetic expressions on either side of the relational operator without using parentheses. For example, in the expression

amount + fee <= balance

both sides (amount + fee and balance) of the < operator are evaluated, and the results are compared. Appendix B shows a table of the Java operators and their precedence.

Comparing Floating-Point Numbers

You have to be careful when comparing floating-point numbers, in order to cope with roundoff errors. For example, the following code multiplies the square root of 2 by itself and then subtracts 2.

double r = Math.sqrt(2);
double d = r * r - 2;
if (d == 0)
   System.out.println("sqrt(2) squared minus 2 is 0");
else
   System.out.println(
      "sqrt(2) squared minus 2 is not 0 but " + d);

Even though the laws of mathematics tell us that (√2)2 – 2 equals 0, this program fragment prints

sqrt(2) squared minus 2 is not 0 but 4.440892098500626E-16

Unfortunately, such roundoff errors are unavoidable. It plainly does not make sense in most circumstances to compare floating-point numbers exactly. Instead, test whether they are close enough.

To test whether a number xis close to zero, you can test whether the absolute value |x| (that is, the number with its sign removed) is less than a very small threshold number. That threshold value is often called ɛ (the Greek letter epsilon). It is common to set ɛ to 10–14 when testing double numbers.

Note

When comparing floating-point numbers, don't test for equality. Instead, check whether they are close enough.

Similarly, you can test whether two numbers are approximately equal by checking whether their difference is close to 0.

Comparing Floating-Point Numbers

In Java, we program the test as follows:

final double EPSILON = 1E-14;
if (Math.abs(x - y) <= EPSILON)
   // x is approximately equal to y

Comparing Strings

To test whether two strings are equal to each other, you must use the method called equals:

if (string1.equals(string2)) ...

Do not use the == operator to compare strings. The expression

if (string1 == string2) // Not useful

has an unrelated meaning. It tests whether the two string variables refer to the identical string object. You can have strings with identical contents stored in different objects, so this test never makes sense in actual programming; see Common Error 5.2 on page 180.

Note

Do not use the == operator to compare strings. Use the equals method instead.

In Java, letter case matters. For example, "Harry" and "HARRY" are not the same string. To ignore the letter case, use the equalsIgnoreCase method:

if (string1.equalsIgnoreCase(string2)) ...

If two strings are not identical to each other, you still may want to know the relationship between them. The compareTo method compares strings in dictionary order. If

string1.compareTo(string2) < 0

then the string string1 comes before the string string2 in the dictionary. For example, this is the case if string1 is "Harry", and string2 is "Hello". If

string1.compareTo(string2) > 0

then string1 comes after string2 in dictionary order. Finally, if

string1.compareTo(string2) == 0

then string1 and string2 are equal.

Note

The compareTo method compares strings in dictionary order.

Actually, the "dictionary" ordering used by Java is slightly different from that of a normal dictionary. Java is case sensitive and sorts characters by putting numbers first, then uppercase characters, then lowercase characters. For example, 1 comes before B, which comes before a. The space character comes before all other characters.

Let us investigate the comparison process closely. When Java compares two strings, corresponding letters are compared until one of the strings ends or the first difference is encountered. If one of the strings ends, the longer string is considered the later one. If a character mismatch is found, the characters are compared to determine which string comes later in the dictionary sequence. This process is called lexicographic comparison. For example, let's compare "car" with "cargo". The first three letters match, and we reach the end of the first string. Therefore "car" comes before "cargo" in the lexicographic ordering. Now compare "cathode" with "cargo". The first two letters match. In the third character position, t comes after r, so the string "cathode" comes after "cargo" in lexicographic ordering. (See Figure 3.)

Lexicographic Comparison

Figure 5.3. Lexicographic Comparison

Table 5.1. Relational Operator Examples

Expression

Value

Comment

3 <= 4

true

3 is less than 4; <= tests for "less than or equal".

Relational Operator Examples

Error

The "less than or equal" operator is <=, not =<, with the "less than" symbol first.

3 > 4

false

> is the opposite of <=.

4 < 4

false

The left-hand side must be strictly smaller than the right-hand side.

4 <= 4

true

Both sides are equal; <= tests for "less than or equal".

3 == 5 - 2

true

== tests for equality.

3 != 5 - 1

true

!= tests for inequality. It is true that 3 is not 5 – 1.

Relational Operator Examples

Error

Use == to test for equality.

1.0 / 3.0 == 0.333333333

false

Although the values are very close to one another, they are not exactly equal. See Common Error 4.3.

Relational Operator Examples

Error

You cannot compare a string to a number.

"Tomato".substring(0, 3).equals("Tom")

true

Always use the equals method to check whether two strings have the same contents.

"Tomato".substring(0, 3) == ("Tom")

false

Never use == to compare strings; it only checks whether the strings are stored in the same location. See Common Error 5.2 on page 180.

"Tom".equalsIgnoreCase("TOM")

true

Use the equalsIgnoreCase method if you don't want to distinguish between uppercase and lowercase letters.

Comparing Objects

If you compare two object references with the == operator, you test whether the references refer to the same object. Here is an example:

Rectangle box1 = new Rectangle(5, 10, 20, 30);
Rectangle box2 = box1;
Rectangle box3 = new Rectangle(5, 10, 20, 30);

The comparison

box1 == box2

is true. Both object variables refer to the same object. But the comparison

box1 == box3

is false. The two object variables refer to different objects (see Figure 4). It does not matter that the objects have identical contents.

You can use the equals method to test whether two rectangles have the same contents, that is, whether they have the same upper-left corner and the same width and height. For example, the test

box1.equals(box3)

is true.

Note

The == operator tests whether two object references are identical. To compare the contents of objects, you need to use the equals method.

Comparing Object References

Figure 5.4. Comparing Object References

However, you must be careful when using the equals method. It works correctly only if the implementors of the class have supplied it. The Rectangle class has an equals method that is suitable for comparing rectangles.

For your own classes, you need to supply an appropriate equals method. You will learn how to do that in Chapter 10. Until that point, you should not use the equals method to compare objects of your own classes.

Testing for null

An object reference can have the special value null if it refers to no object at all. It is common to use the null value to indicate that a value has never been set. For example,

String middleInitial = null; // Not set
if ( ... )
   middleInitial = middleName.substring(0, 1);

Note

The null reference refers to no object.

You use the == operator (and not equals) to test whether an object reference is a null reference:

if (middleInitial == null)
   System.out.println(firstName + " " + lastName);
else
   System.out.println(firstName + " " + middleInitial + ". " + lastName);

Note that the null reference is not the same as the empty string "". The empty string is a valid string of length 0, whereas a null indicates that a string variable refers to no string at all.

Testing for null
  1. the empty string ""?

  2. the string " " containing a space?

  3. null?

4. Which of the following comparisons are syntactically incorrect? Which of them are syntactically correct, but logically questionable?

String a = "1";
String b = "one";
double x = 1;
double y = 3 * (1.0 / 3);
  1. a == "1"

  2. a == null

  3. a.equals("")

  4. a == b

  5. a == x

  6. x == y

  7. x - y == null

  8. x.equals(y)

Multiple Alternatives

Sequences of Comparisons

Many computations require more than a single if/else decision. Sometimes, you need to make a series of related comparisons.

The following program asks for a value describing the magnitude of an earthquake on the Richter scale and prints a description of the likely impact of the quake. The Richter scale is a measurement for the strength of an earthquake. Every step in the scale, for example from 6.0 to 7.0, signifies a tenfold increase in the strength of the quake. The 1989 Loma Prieta earthquake that damaged the Bay Bridge in San Francisco and destroyed many buildings in several Bay area cities registered 7.1 on the Richter scale.

Note

Multiple conditions can be combined to evaluate complex decisions. The correct arrangement depends on the logic of the problem to be solved.

ch05/quake/Earthquake.java

1   /**
2      A class that describes the effects of an earthquake.
3   */
4   public class Earthquake
5   {
6      private double richter;
7
8      /**
9         Constructs an Earthquake object.
10         @param magnitude the magnitude on the Richter scale
11      */
12      public Earthquake(double magnitude)
13      {
14           richter = magnitude;
15      }
16
17      /**
18         Gets a description of the effect of the earthquake.
19         @return the description of the effect
20      */
21      public String getDescription()
22      {
23      String r;
24      if (richter >= 8.0)
25         r = "Most structures fall";
26      else if (richter >= 7.0)
27         r = "Many buildings destroyed";
28      else if (richter >= 6.0)
29         r = "Many buildings considerably damaged, some collapse";
30      else if (richter >= 4.5)
31         r = "Damage to poorly constructed buildings";
32      else if (richter >= 3.5)
33         r = "Felt by many people, no destruction";
34      else if (richter >= 0)
35         r = "Generally not felt by people";
36      else
37         r = "Negative numbers are not valid";
38      return r;
39     }
40  }

ch05/quake/EarthquakeRunner.java

1  import java.util.Scanner;
2
3  /**
4     This program prints a description of an earthquake of a given magnitude
5  */
6  public class EarthquakeRunner
7  {
8     public static void main(String[] args)
9     {
10        Scanner in = new Scanner(System.in);
11
12        System.out.print("Enter a magnitude on the Richter scale: ");
13        double magnitude = in.nextDouble();
14        Earthquake quake = new Earthquake(magnitude);
15        System.out.println(quake.getDescription());
16     }
17  }

Program Run

Enter a magnitude on the Richter scale: 7.1
Many buildings destroyed

Here we must sort the conditions and test against the largest cutoff first. Suppose we reverse the order of tests:

if (richter >= 0) // Tests in wrong order
   r = "Generally not felt by people";
else if (richter >= 3.5)
   r = "Felt by many people, no destruction";
else if (richter >= 4.5)
   r = "Damage to poorly constructed buildings";
else if (richter >= 6.0)
   r = "Many buildings considerably damaged, some collapse";
else if (richter >= 7.0)
   r = "Many buildings destroyed";
else if (richter >= 8.0)
   r = "Most structures fall";

This does not work. All nonnegative values of richter fall into the first case, and the other tests will never be attempted.

In this example, it is also important that we use an if/else if/else test, not just multiple independent if statements. Consider this sequence of independent tests:

if (richter >= 8.0) // Didn't use  else
   r = "Most structures fall";
if (richter >= 7.0)
   r = "Many buildings destroyed";
if (richter >= 6.0)
   r = "Many buildings considerably damaged, some collapse";
if (richter >= 4.5)
   r = "Damage to poorly constructed buildings";
if (richter >= 3.5)
   r = "Felt by many people, no destruction";
if (richter >= 0)
    r = "Generally not felt by people";

Now the alternatives are no longer exclusive. If richter is 6.0, then the last four tests all match, and r is set four times.

Nested Branches

Some computations have multiple levels of decision making. You first make one decision, and each of the outcomes leads to another decision. Here is a typical example.

In the United States, taxpayers pay federal income tax at different rates depending on their incomes and marital status. There are two main tax schedules: one for single taxpayers and one for married taxpayers "filing jointly", meaning that the married taxpayers add their incomes together and pay taxes on the total. Table 2 gives the tax rate computations for each of the filing categories, using a simplified version of the values for the 2008 federal tax return.

Table 5.2. Federal Tax Rate Schedule (2008, simplified)

If your filing status is Single:

If your filing status is Married:

Tax Bracket

Percentage

Tax Bracket

Percentage

$0 . . . $32,000

10%

$0 . . . $64,000

10%

Amount over $32,000

25%

Amount over $64,000

25%

Income Tax Computation Using Simplified 2008 Schedule

Figure 5.5. Income Tax Computation Using Simplified 2008 Schedule

Now let us compute the taxes due, given a filing status and an income figure. First, we must branch on the filing status. Then, for each filing status, we must have another branch on income level.

The two-level decision process is reflected in two levels of if statements. We say that the income test is nested inside the test for filing status. (See Figure 5 for a flowchart.)

ch05/tax/TaxReturn.java

1  /**
2     A tax return of a taxpayer in 2008.
3  */
4  public class TaxReturn
5  {
6     public static final int SINGLE = 1;
7     public static final int MARRIED = 2;
8
9     private static final double RATE1 = 0.10;
10     private static final double RATE2 = 0.25;
11     private static final double RATE1_SINGLE_LIMIT = 32000;
12     private static final double RATE1_MARRIED_LIMIT = 64000;
13
14     private double income;
15     private int status;
16
17     /**
18        Constructs a TaxReturn object for a given income and
19        marital status.
20        @param anIncome the taxpayer income
21        @param aStatus either SINGLE or MARRIED
22     */
23     public TaxReturn(double anIncome, int aStatus)
24     {
25        income = anIncome;
26        status = aStatus;
27     }
28
29     public double getTax()
30     {
31        double tax1 = 0;
32        double tax2 = 0;
33
34     if (status == SINGLE)
35     {
36         if (income <= RATE1_SINGLE_LIMIT)
37         {
38            tax1 = RATE1 * income;
39         }
40         else
41         {
42            tax1 = RATE1 * RATE1_SINGLE_LIMIT;
43            tax2 = RATE2 * (income - RATE1_SINGLE_LIMIT);
44         }
45     }
46     else
47     {
48          if (income <= RATE1_MARRIED_LIMIT)
49          {
50             tax1 = RATE1 * income;
51          }
52          else
53          {
54             tax1 = RATE1 * RATE1_MARRIED_LIMIT;
55             tax2 = RATE2 * (income - RATE1_MARRIED_LIMIT);
56          }
57     }
58
59     return tax1 + tax2;
60    }
61  }

ch05/tax/TaxCalculator.java

1  import java.util.Scanner;
2
3  /**
4     This program calculates a simple tax return.
5  */
6  public class TaxCalculator
7  {
8     public static void main(String[] args)
9     {
10       Scanner in = new Scanner(System.in);
11
12       System.out.print("Please enter your income: ");
13       double income = in.nextDouble();
14
15       System.out.print("Are you married? (Y/N) ");
16       String input = in.next();
17       int status;
18       if (input.equalsIgnoreCase("Y"))
19          status = TaxReturn.MARRIED;
20       else
21          status = TaxReturn.SINGLE;
22       TaxReturn aTaxReturn = new TaxReturn(income, status);
23
24       System.out.println("Tax: "
25             + aTaxReturn.getTax());
26    }
27  }

Program Run

Please enter your income: 80000
Are you married? (Y/N) Y
Tax: 10400.0
Income Tax Computation Using Simplified 2008 Schedule

6. Some people object to higher tax rates for higher incomes, claiming that you might end up with less money after taxes when you get a raise for working hard. What is the flaw in this argument?

Using Boolean Expressions

The boolean Type

In Java, an expression such as amount < 1000 has a value, just as the expression amount + 1000 has a value. The value of a relational expression is either true or false. For example, if amount is 500, then the value of amount < 1000 is true. Try it out: The program fragment

double amount = 0;
System.out.println(amount < 1000);

prints true. The values true and false are not numbers, nor are they objects of a class. They belong to a separate type, called boolean. The Boolean type is named after the mathematician George Boole (1815–1864), a pioneer in the study of logic.

Note

The boolean type has two values: true and false.

The boolean Type

Predicate Methods

A predicate method is a method that returns a boolean value. Here is an example of a predicate method:

public class BankAccount
{
   public boolean isOverdrawn()
   {
      return balance < 0; // Returns true or false
   }
}

Note

A predicate method returns a boolean value.

You can use the return value of the method as the condition of an if statement:

if (harrysChecking.isOverdrawn()) ...

There are several useful static predicate methods in the Character class:

isDigit
isLetter
isUpperCase
isLowerCase

that let you test whether a character is a digit, a letter, an uppercase letter, or a lowercase letter:

if (Character.isUpperCase(ch)) . . .

It is a common convention to give the prefix "is" or "has" to the name of a predicate method.

The Scanner class has useful predicate methods for testing whether the next input will succeed. The hasNextInt method returns true if the next character sequence denotes an integer. It is a good idea to call that method before calling nextInt:

if (in.hasNextInt()) input = in.nextInt();

Similarly, the hasNextDouble method tests whether a call to nextDouble will succeed.

The Boolean Operators

Suppose you want to find whether amount is between 0 and 1000. Then two conditions have to be true: amount must be greater than 0, and it must be less than 1000. In Java you use the && operator to represent the and when combining test conditions. That is, you can write the test as follows:

if (0 < amount && amount < 1000) . . .

The && (and) operator combines several tests into a new test that passes only when all conditions are true. An operator that combines Boolean values is called a Boolean operator.

Note

You can form complex tests with the Boolean operators && (and), || (or), and ! (not).

The && operator has a lower precedence than the relational operators. For that reason, you can write relational expressions on either side of the && operator without using parentheses. For example, in the expression

0 < amount && amount < 1000

the expressions 0 < amount and amount < 1000 are evaluated first. Then the && operator combines the results. Appendix B shows a table of the Java operators and their precedence.

The || (or) logical operator also combines two or more conditions. The resulting test succeeds if at least one of the conditions is true. For example, here is a test to check whether the string input is an "S" or "M":

if (input.equals("S") || input.equals("M")) ...

Figure 6 shows flowcharts for these examples.

Sometimes you need to invert a condition with the ! (not) logical operator. For example, we may want to carry out a certain action only if two strings are not equal:

if (!input.equals("S")) ...
Flowcharts for && and || Combinations

Figure 5.6. Flowcharts for && and || Combinations

The ! operator takes a single condition and evaluates to true if that condition is false and to false if the condition is true.

Table 5.3. Boolean Operators

Expression

Value

Comment

0 < 200 && 200 < 100

false

Only the first condition is true.

0 < 200 || 200 < 100

true

The first condition is true.

0 < 200 || 100 < 200

true

The || is not a test for "either-or". If both conditions are true, the result is true.

Boolean Operators

Syntax error

Error: The expression 0 < 100 is true, which cannot be compared against 200.

Boolean Operators

true

Error: This condition is always true. The programmer probably intended 0 < x && x < 100. (See Common Error 5.5).

0 < x && x < 100 || x == −1

(0 < x && x < 100) || x == −1

The && operator binds more strongly than the || operator. (See Appendix B.)

!(0 < 200)

false

0 < 200 is true, therefore its negation is false.

frozen == true

frozen

There is no need to compare a Boolean variable with true.

frozen == false

!frozen

It is clearer to use ! than to compare with false.

Here is a summary of the three logical operations:

A

B

A && B

A

B

A || B

A

A !

true

true

true

true

Any

true

true

false

true

false

false

false

true

true

false

true

false

Any

false

false

false

false

  

Using Boolean Variables

You can use a Boolean variable if you know that there are only two possible values. Have another look at the tax program in Section 5.3.2. The marital status is either single or married. Instead of using an integer, you can use a variable of type boolean:

private boolean married;

The advantage is that you can't accidentally store a third value in the variable.

Then you can use the Boolean variable in a test:

if (married)
    ...
else
    ...

Note

You can store the outcome of a condition in a Boolean variable.

Sometimes Boolean variables are called flags because they can have only two states: "up" and "down".

It pays to think carefully about the naming of Boolean variables. In our example, it would not be a good idea to give the name maritalStatus to the Boolean variable. What does it mean that the marital status is true? With a name like married there is no ambiguity; if married is true, the taxpayer is married.

By the way, it is considered gauche to write a test such as

if (married == true) . . . // Don't

Just use the simpler test

if (married) . . .

In Chapter 6 we will use Boolean variables to control complex loops.

Using Boolean Variables
System.out.println(x > 0 || x < 0);
print false?

8. Rewrite the following expression, avoiding the comparison with false:

if (Character.isDigit(ch) == false) ...

Code Coverage

Testing the functionality of a program without consideration of its internal structure is called black-box testing. This is an important part of testing, because, after all, the users of a program do not know its internal structure. If a program works perfectly on all inputs, then it surely does its job.

Note

Black-box testing describes a testing method that does not take the structure of the implementation into account.

However, it is impossible to ensure absolutely that a program will work correctly on all inputs just by supplying a finite number of test cases. As the famous computer scientist Edsger Dijkstra pointed out, testing can show only the presence of bugs—not their absence. To gain more confidence in the correctness of a program, it is useful to consider its internal structure. Testing strategies that look inside a program are called white-box testing. Performing unit tests of each method is a part of white-box testing.

Note

White-box testing uses information about the structure of a program.

You want to make sure that each part of your program is exercised at least once by one of your test cases. This is called code coverage. If some code is never executed by any of your test cases, you have no way of knowing whether that code would perform correctly if it ever were executed by user input. That means that you need to look at every if/else branch to see that each of them is reached by some test case. Many conditional branches are in the code only to take care of strange and abnormal inputs, but they still do something. It is a common phenomenon that they end up doing something incorrectly, but those faults are never discovered during testing, because nobody supplied the strange and abnormal inputs. Of course, these flaws become immediately apparent when the program is released and the first user types in an unusual input and is incensed when the program misbehaves. The remedy is to ensure that each part of the code is covered by some test case.

Note

Code coverage is a measure of how many parts of a program have been tested.

For example, in testing the getTax method of the TaxReturn class, you want to make sure that every if statement is entered for at least one test case. You should test both single and married taxpayers, with incomes in each of the three tax brackets.

When you select test cases, you should make it a habit to include boundary test cases: legal values that lie at the boundary of the set of acceptable inputs.

For example, what happens when you compute the taxes for an income of 0 or if a bank account has an interest rate of 0 percent? Boundary cases are still legitimate inputs, and you expect that the program will handle them correctly—often in some trivial way or through special cases. Testing boundary cases is important, because programmers often make mistakes dealing with boundary conditions. Division by zero, extracting characters from empty strings, and accessing null references are common symptoms of boundary errors.

Note

Boundary test cases are test cases that are at the boundary of acceptable inputs.

Code Coverage

10. Give a boundary test case for the EarthquakeRunner program. What output do you expect?

Summary of Learning Objectives

Use the if statement to implement a decision.

  • The if statement lets a program carry out different actions depending on a condition.

  • A block statement groups several statements together.

Implement comparisons of numbers and objects.

  • Relational operators compare values. The == operator tests for equality.

  • When comparing floating-point numbers, don't test for equality. Instead, check whether they are close enough.

  • Do not use the == operator to compare strings. Use the equals method instead.

  • The compareTo method compares strings in dictionary order.

  • The == operator tests whether two object references are identical. To compare the contents of objects, you need to use the equals method.

  • The null reference refers to no object.

Implement complex decisions that require multiple if statements.

  • Multiple conditions can be combined to evaluate complex decisions. The correct arrangement depends on the logic of the problem to be solved.

Use the Boolean data type to store and combine conditions that can be true or false.

  • The boolean type has two values: true and false.

  • A predicate method returns a boolean value.

  • You can form complex tests with the Boolean operators && (and), || (or), and ! (not).

  • De Morgan's law shows how to simplify expressions in which the not operator (!) is applied to terms joined by the && or || operators.

  • You can store the outcome of a condition in a Boolean variable.

Design test cases that cover all parts of a program.

  • Black-box testing describes a testing method that does not take the structure of the implementation into account.

  • White-box testing uses information about the structure of a program.

  • Code coverage is a measure of how many parts of a program have been tested.

  • Boundary test cases are test cases that are at the boundary of acceptable inputs.

  • You should calculate test cases by hand to double-check that your application computes the correct answer.

Use the Java logging library for messages that can be easily turned on or off.

  • Logging messages can be deactivated when testing is complete.

Classes, Objects, and Methods Introduced in this Chapter

java.lang.Character                       java.util.Scanner
   isDigit                                   hasNextDouble
   isLetter                                  hasNextInt
   isLowerCase                            java.util.logging.Level
   isUpperCase                               INFO
java.lang.Object                             OFF
   equals                                 java.util.logging.Logger
java.lang.String                             getGlobal
   equals                                    info
   equalsIgnoreCase                          setLevel
   compareTo

Media Resources

  • Worked Example Extracting the Middle

  • • Lab Exercises

  • Media Resources
  • Media Resources

Review Exercises

R5.1 What is the value of each variable after the if statement?

  1. int n = 1; int k = 2; int r = n; if (k < n) r = k;

  2. int n = 1; int k = 2; int r; if (n < k) r = k; else r = k + n;

  3. int n = 1; int k = 2; int r = k; if (r < k) n = r; else k = n;

  4. int n = 1; int k = 2; int r = 3; if (r < n + k) r = 2 * n; else k = 2 * r;

R5.2 Find the errors in the following if statements.

  1. if (1 + x > Math.pow(x, Math.sqrt(2)) y = y + x;

  2. if (x = 1) y++; else if (x = 2) y = y + 2;

  3. int x = Integer.parseInt(input); if (x != null) y = y + x;

R5.3 Find the error in the following if statement that is intended to select a language from a given country and state/province.

language = "English";
if (country.equals("Canada"))
   if (stateOrProvince.equals("Quebec")) language = "French";
else if (country.equals("China"))
   language = "Chinese";

R5.4 Find the errors in the following if statements.

  1. if (x && y == 0) { x = 1; y = 1; }

  2. if (1 <= x <= 10)
       System.out.println(x);
  3. if (!s.equals("nickels") || !s.equals("pennies")
          || !s.equals("dimes") || !s.equals("quarters"))
       System.out.print("Input error!");
  4. if (input.equalsIgnoreCase("N") || "NO")
       return;

R5.5 Explain the following terms, and give an example for each construct:

  1. Expression

  2. Condition

  3. Statement

  4. Simple statement

  5. Compound statement

  6. Block

R5.6 Explain the difference between an if statement with multiple else branches and nested if statements. Give an example for each.

R5.7 Give an example for an if/else if/else statement where the order of the tests does not matter. Give an example where the order of the tests matters.

R5.8 Of the following pairs of strings, which comes first in lexicographic order?

  1. "Tom", "Jerry"

  2. "Tom", "Tomato"

  3. "church", "Churchill"

  4. "car manufacturer", "carburetor"

  5. "Harry", "hairy"

  6. "C++", " Car"

  7. "Tom", "Tom"

  8. "Car", "Carl"

  9. "car", "bar"

  10. "101", "11"

  11. "1.01", "10.1"

R5.9 Complete the following truth table by finding the truth values of the Boolean expressions for all combinations of the Boolean inputs p, q, and r.

p

q

r

(p && q) || !r

!(p && (q || !r))

false

false

false

  

false

false

false

  

false

false

false

  

. . .

. . .

. . .

  

5 more combinations

  

. . .

  

R5.10 Each square on a chess board can be described by a letter and number, such as g5 in this example:

Review Exercises

The following pseudocode describes an algorithm that determines whether a square with a given letter and number is dark (black) or light (white).

If the letter is an a, c, e, or g
    If the number is odd
    color = "black"
    Else
    color = "white"
Else
    If the number is even
    color = "black"
    Else
    color = "white"

Using the procedure in Productivity Hint 5.2 on page 192, trace this pseudocode with input g5.

R5.11 Give a set of four test cases for the algorithm of Exercise R5.10 that covers all branches.

R5.12 In a scheduling program, we want to check whether two appointments overlap. For simplicity, appointments start at a full hour, and we use military time (with hours 0–24). The following pseudocode describes an algorithm that determines whether the appointment with start time start1 and end time end1 overlaps with the appointment with start time start2 and end time end2.

If start1 > start2
    s = start1
Else
    s = start2
If end1 < end2
    e = endl
Else
    e = end2
If s < e
    The appointments overlap.
Else
    The appointments

Trace this algorithm with an appointment from 10–12 and one from 11–13, then with an appointment from 10–11 and one from 12–13.

R5.13 Write pseudocode for a program that prompts the user for a month and day and prints out whether it is one of the following four holidays:

  • New Year's Day (January 1)

  • Independence Day (July 4)

  • Veterans Day (November 11)

  • Christmas Day (December 25)

R5.14 True or false? A && B is the same as B && A for any Boolean conditions A and B.

R5.15 Explain the difference between

s = 0;
if (x > 0) s++;
if (y > 0) s++;

and

s = 0;
if (x > 0) s++;
else if (y > 0) s++;

R5.16 Use de Morgan's law to simplify the following Boolean expressions.

  1. !(x > 0 && y > 0)

  2. !(x != 0 || y != 0)

  3. !(country.equals("US") && !state.equals("HI")
          && !state.equals("AK"))
  4. !(x % 4 != 0 || !(x % 100 == 0 && x % 400 == 0))

R5.17 Make up another Java code example that shows the dangling else problem, using the following statement: A student with a GPA of at least 1.5, but less than 2, is on probation; with less than 1.5, the student is failing.

R5.18 Explain the difference between the == operator and the equals method when comparing strings.

R5.19 Explain the difference between the tests

r == s

and

r.equals(s)

where both r and s are of type Rectangle.

R5.20 What is wrong with this test to see whether r is null? What happens when this code runs?

Rectangle r;
...
if (r.equals(null))
   r = new Rectangle(5, 10, 20, 30);

R5.21 Explain how the lexicographic ordering of strings differs from the ordering of words in a dictionary or telephone book. Hint: Consider strings, such as IBM, wiley.com, Century 21, While-U-Wait, and 7-11.

R5.22 Write Java code to test whether two objects of type Line2D.Double represent the same line when displayed on the graphics screen. Do not use a.equals(b).

Line2D.Double a;
Line2D.Double b;

if (your condition goes here)
   g2.drawString("They look the same!", x, y);

Hint: If p and q are points, then Line2D.Double(p, q) and Line2D.Double(q, p) look the same.

R5.23 Explain why it is more difficult to compare floating-point numbers than integers. Write Java code to test whether an integer n equals 10 and whether a floating-point number x is approximately equal to 10.

R5.24 Consider the following test to see whether a point falls inside a rectangle.

Point2D.Double p = . . .
Rectangle r = . . .
boolean xInside = false;
if (r.getX() <= p.getX() && p.getX() <= r.getX() + r.getWidth())
    xInside = true;
boolean yInside = false;
if (r.getY() <= p.getY() && p.getY() <= r.getY() + r.getHeight())
    yInside = true;
if (xInside && yInside)
   g2.drawString("p is inside the rectangle.",
         p.getX(), p.getY());

Rewrite this code to eliminate the explicit true and false values, by setting xInside and yInside to the values of Boolean expressions.

R5.25 Give a set of test cases for the earthquake program in Section 5.3.1. Ensure coverage of all branches.

R5.26 Give an example of a boundary test case for the earthquake program in Section 5.3.1. What result do you expect?

Programming Exercises

P5.1 Write a program that prints all real solutions to the quadratic equation ax2 + bx + c = 0. Read in a, b, c and use the quadratic formula. If the discriminant b2 – 4ac is negative, display a message stating that there are no real solutions. Implement a class QuadraticEquation whose constructor receives the coefficients a, b, c of the quadratic equation. Supply methods getSolution1 and getSolution2 that get the solutions, using the quadratic formula, or 0 if no solution exists. The getSolution1 method should return the smaller of the two solutions. Supply a method

boolean hasSolutions()

that returns false if the discriminant is negative.

P5.2 Write a program that takes user input describing a playing card in the following shorthand notation:

Notation

Meaning

A

Ace

2 . . . 10

Card values

J

Jack

Q

Queen

K

King

D

Diamonds

H

Hearts

S

Spades

C

Clubs

Your program should print the full description of the card. For example,

Enter the card notation:
4S
Four of spades

Implement a class Card whose constructor takes the card notation string and whose getDescription method returns a description of the card. If the notation string is not in the correct format, the getDescription method should return the string "Unknown".

P5.3 Write a program that reads in three floating-point numbers and prints the three inputs in sorted order. For example:

Please enter three numbers:
4
9
2.5
The inputs in sorted order are:
2.5
4
9

P5.4 Write a program that translates a letter grade into a number grade. Letter grades are A B C D F, possibly followed by + or -. Their numeric values are 4, 3, 2, 1, and 0. There is no F+ or F-. A + increases the numeric value by 0.3, a - decreases it by 0.3. However, an A+ has the value 4.0. All other inputs have value −1.

Enter a letter grade:
B-
Numeric value: 2.7.

Use a class Grade with a method getNumericGrade.

P5.5 Write a program that translates a number into the closest letter grade. For example, the number 2.8 (which might have been the average of several grades) would be converted to B-. Break ties in favor of the better grade; for example, 2.85 should be a B. Any value ≥ 4.15 should be an A+.

Use a class Grade with a method getLetterGrade.

P5.6 Write a program that reads in three strings and prints them in lexicographically sorted order:

Please enter three strings:
Tom
Dick
Harry
The inputs in sorted order are: Dick
Harry
Tom

P5.7 Change the implementation of the getTax method in the TaxReturn class, by setting a variable rate1Limit, depending on the marital status. Then have a single formula that computes the tax, depending on the income and the limit. Verify that your results are identical to that of the TaxReturn class in this chapter.

P5.8 The original U.S. income tax of 1913 was quite simple. The tax was

  • 1 percent on the first $50,000.

  • 2 percent on the amount over $50,000 up to $75,000.

  • 3 percent on the amount over $75,000 up to $100,000.

  • 4 percent on the amount over $100,000 up to $250,000.

  • 5 percent on the amount over $250,000 up to $500,000.

  • 6 percent on the amount over $500,000.

There was no separate schedule for single or married taxpayers. Write a program that computes the income tax according to this schedule.

P5.9 Write a program that prompts for the day and month of the user's birthday and then prints a horoscope. Make up fortunes for programmers, like this:

Please enter your birthday (month and day): 6 16
Gemini are experts at figuring out the behavior of complicated programs.
You feel where bugs are coming from and then stay one step ahead. Tonight,
your style wins approval from a tough critic.

Each fortune should contain the name of the astrological sign. (You will find the names and date ranges of the signs at a distressingly large number of sites on the Internet.)

P5.10 When two points in time are compared, each given as hours (in military time, ranging from 0 and 23) and minutes, the following pseudocode determines which comes first.

If hour1 < hour2
    time1 comes first.
Else if hour1 and hour2 are the same
    If minute1 < minute2
    time1 comes first.
    Else if minute1 and minute2 are the same
    time1 and time2 are the same.
    Else
    time2 comes first.
Else
    time2 comes first.

Write a program that prompts the user for two points in time and prints the time that comes first, then the other time.

P5.11 The following algorithm yields the season (Spring, Summer, Fall, or Winter) for a given month and day.

If month is 1, 2, or 3, season = "Winter"
Else if month is 4, 5, or 6, season = "Spring"
Else if month is 7, 8, or 9, season = "Summer"
Else if month is 10, 11, or 12, season = "Fall"
If month is divisible by 3 and day >= 21
    If season is "Winter", season = "Spring"
    Else if season is "Spring", season = "Summer"
    Else if season is "Summer", season = "Fall"
    Else season = "Winter"

Write a program that prompts the user for a month and day and then prints the season, as determined by this algorithm.

P5.12 A year with 366 days is called a leap year. A year is a leap year if it is divisible by 4 (for example, 1980). However, since the introduction of the Gregorian calendar on October 15, 1582, a year is not a leap year if it is divisible by 100 (for example, 1900); however, it is a leap year if it is divisible by 400 (for example, 2000). Write a program that asks the user for a year and computes whether that year is a leap year. Implement a class Year with a predicate method boolean isLeapYear().

P5.13 Write a program that asks the user to enter a month (1 = January, 2 = February, and so on) and then prints the number of days of the month. For February, print "28 days".

Enter a month (1-12):
5
31 days

Implement a class Month with a method int getDays(). Do not use a separate if or else statement for each month. Use Boolean operators.

P5.14 Write a program that reads in two floating-point numbers and tests (a) whether they are the same when rounded to two decimal places and (b) whether they differ by less than 0.01. Here are two sample runs.

Enter two floating-point numbers:
2.0
1.99998
They are the same when rounded to two decimal places.
They differ by less than 0.01.

Enter two floating-point numbers:
0.999
0.991
They are different when rounded to two decimal places.
They differ by less than 0.01.

P5.15 Enhance the BankAccount class of Chapter 3 by

  • Rejecting negative amounts in the deposit and withdraw methods

  • Rejecting withdrawals that would result in a negative balance

P5.16 Write a- program that reads in the hourly wage of an employee. Then ask how many hours the employee worked in the past week. Be sure to accept fractional hours. Compute the pay. Any overtime work (over 40 hours per week) is paid at 150 percent of the regular wage. Solve this problem by implementing a class Paycheck.

P5.17 Write a unit conversion program that asks users to identify the unit from which they want to convert and the unit to which they want to convert. Legal units are in, ft, mi, mm, cm, m, and km. Declare two objects of a class UnitConverter that convert between meters and a given unit.

Convert from:
in
Convert to:
mm
Value:
10
10 in = 254 mm

P5.18 A line in the plane can be specified in various ways:

  • by giving a point (x, y) and a slope m

  • by giving two points (x1, y1), (x2, y2)

  • as an equation in slope-intercept form y = mx + b

  • as an equation x = a if the line is vertical

Implement a class Line with four constructors, corresponding to the four cases above. Implement methods

boolean intersects(Line other)
boolean equals(Line other)
boolean isParallel(Line other)

P5.19 Write a program that draws a circle with radius 100 and center (200, 200). Ask the user to specify the x- and y-coordinates of a point. Draw the point as a small circle. If the point lies inside the circle, color the small circle green. Otherwise, color it red. In your exercise, declare a class Circle and a method boolean isInside(Point2D.Double p).

P5.20 Write a graphics program that asks the user to specify the radii of two circles. The first circle has center (100, 200), and the second circle has center (200, 100). Draw the circles. If they intersect, then color both circles green. Otherwise, color them red. Hint: Compute the distance between the centers and compare it to the radii. Your program should draw nothing if the user enters a negative radius. In your exercise, declare a class Circle and a method boolean intersects(Circle other).

Programming Projects

Project 5.1 Implement a combination lock class. A combination lock has a dial with 26 positions labeled A . . . Z. The dial needs to be set three times. If it is set to the correct combination, the lock can be opened. When the lock is closed again, the combination can be entered again. If a user sets the dial more than three times, the last three settings determine whether the lock can be opened. An important part of this exercise is to implement a suitable interface for the CombinationLock class.

Project 5.2 Get the instructions for last year's form 1040 from http://www.irs.ustreas.gov. Find the tax brackets that were used last year for all categories of taxpayers (single, married filing jointly, married filing separately, and head of household). Write a program that computes taxes following that schedule. Ignore deductions, exemptions, and credits. Simply apply the tax rate to the income.

Answers to Self-Check Questions

  1. If the withdrawal amount equals the balance, the result should be a zero balance and no penalty.

  2. Only the first assignment statement is part of the if statement. Use braces to group both assignment statements into a block statement.

  3. (a) 0; (b) 1; (c) An exception occurs.

  4. Syntactically incorrect: e, g, h. Logically questionable: a, d, f

  5. Yes, if you also reverse the comparisons:

    if (richter < 3.5)
       r = "Generally not felt by people";
    else if (richter < 4.5)
       r = "Felt by many people, no destruction";
    else if (richter < 6.0)
       r = "Damage to poorly constructed buildings";
    ...
  6. The higher tax rate is only applied on the income in the higher bracket. Suppose you are single and make $31,900. Should you try to get a $200 raise? Absolutely: you get to keep 90 percent of the first $100 and 75 percent of the next $100.

  7. When x is zero.

  8. if (!Character.isDigit(ch)) . . .

  9. Seven

  10. An input of 0 should yield an output of "Generally not felt by people". (If the output is "Negative numbers are not allowed", there is an error in the program.)

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

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