Making Decisions

As was mentioned at the start of the Introducing Iteration exercises, iteration is one of two extremely important basic concepts in programming. The second of these, which we will be covering in this set of exercises, is selection, or making decisions. Like iteration, selection is used to affect the flow of control in a program, in a well-specified and controlled way. The notion of planned changes to the normal sequential flow of control should be contrasted with the behaviour of exceptions introduced in a previous exercise set. While you can (and should) plan for exceptions to occur, you cannot in general predict the exact point in the execution of your program where an exception will be raised. The selection statements presented here are, however, completely predictable in their behaviour.

This set of exercises will cover the two selection statements provided by Java: the if statement and the switch statement. Solutions to the exercises are available here.

Code for these notes can be found in the directory

/import/teaching/BSc/1st/ItP/ifswitch

The if statement

Like the for loop in the world of iteration, the if statement is the most general of the selection statements offered by Java; just as for any while or do loop there is an equivalent for loop, for every switch statement there is an equivalent if statement, although the reverse if not true. Of course, just as with for loops, is is possible to write an extremely complicated if statement that even the most experienced programmer will have difficulty understanding!

The basic form of an if statement is shown below:

if (<condition>) {
   <body>
}
    

The condition and body parts of the statement have the same meanings as they would in a for statement; however, in the case of the if statement the body is executed if (and only if) the condition evaluates to true. For example, the println function call in the following statement will only be executed if the value if x is equal to 5. There are no other circumstances under which the body of this statement will be executed.

if (x == 5) {
   System.out.println("x equals 5!");
}
    

As you would expect, the body of the statement may be any valid block of Java code that might also appear in the body of a function or a loop. The body can declare new variables, call functions and methods, or contain further if or loop statements (a selection or iteration statement occurring inside the body of body of another is often referred to as a nested statement, as in 'nested loop' or 'nested if'). For example:

if (x < 10) {
   if (x > 5) {
      int j = 0;
      for (int i = 0; i < x; i = i + 1) {
         j = j + i;
      }
      System.err.println("Sum = " + j);
   }
}
    

The condition part of the if statement can be any valid Java conditional expression. It is often convenient to use the inequality operator ! to reverse the sense of the condition, that is, the body of the statement will be executed only if the rest of the conditional expression evaluates to false. For instance, the following two statements are equivalent:

if (x != 5) {
   ...
}
    
if (!(x == 5)) {
   ...
} 
    

The if-else statement

We quite frequently want to perform one set of actions is some condition is true, but another different set of actions if the same condition is not true. Consider, for example, the behaviour of an automatic teller machine (ATM) when a customer attempts to withdraw X pounds from their account: The logic used by the machine is probably something similar to "if the balance of the account is greater than or equal to X, allow the withdrawal. Otherwise, reject the request (and swallow the customers' card!)".

How do we express such logic in a program? We could use two if statements with 'opposite' conditions, as shown below (this program is available as Prog41.java in the usual directory):

1  import java.io.*;
2  
3  class Prog41 {
4  
5    // The wrong way to do an if-else statement...
6    public static void main(String args[]) throws IOException {
7  
8      BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
9      int balance = 100;
10 
11     System.out.print("Enter amount to withdraw: £");
12 
13     int x = Integer.parseInt(in.readLine());
14 
15     if (balance >= x) {
16       // Perform withdrawal
17       System.out.println("Withdrawing £" + x);
18       balance = balance - x;
19     }
20     if (balance < x) {
21       // Reject request
22       System.out.println("You don't have £" + x);
23     }
24 
25     System.out.println("Final balance: £" + balance);
26   }
27 }
    

Unfortunately, this program has a subtle flaw. Can you see what it is? (Hint: try to withdraw more than half the available balance). Clearly this is not a Good Thing: you would not want to use an ATM that accepted a withdrawal, gave you the money, then swallowed your card anyway! A closer examination of the program will reveal what is happening here. Say we attempt to withdraw £60. The condition on the first if statement evaluates to true, since 60 is less than 100. The withdrawal is performed and the balance is reduced appropriately. So far, so good, but think about what happens next. The computer has no way of knowing that you only wanted to execute one of the if statements, so it goes ahead and evaluates the condition on the second if. This expression also evaluates to true, because the balance is now only £40 after the successful withdrawal. Thus the ATM prints a rude message and keeps your card, despite having just given you sixty pounds!

The problem here stems from the fact that both conditional expressions are always evaluated, even if the first if statement succeeds. Because the body of the first if statement changes the value of a variable used in the second if statement, it is possible for both conditions to evaluate to true even though they appear to be the negation of one another. In this particular case we could fix the program by reversing the order of the if statements (think about why this solves the problem), but obviously this solution will not work in every situation. Using two if statements in this way is also inefficient, as we always evaluate two conditions when one should be sufficient. Fortunately, Java (and almost every other programming language) provides a solution to this dilemma, in the form of the if-else statement. Prog42 below shows a better version of our ATM program:

1  import java.io.*;
2  
3  class Prog42 {
4  
5    // The right way to do an if-else statement...
6    public static void main(String args[]) throws IOException {
7  
8      BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
9      int balance = 100;
10 
11     System.out.print("Enter amount to withdraw: £");
12 
13     int x = Integer.parseInt(in.readLine());
14 
15     if (balance >= x) {
16       // Perform withdrawal
17       System.out.println("Withdrawing £" + x);
18       balance = balance - x;
19     }
20     else {
21       // Reject request
22       System.out.println("You don't have £" + x);
23     }
24 
25     System.out.println("Final balance: £" + balance);
26   }
27 }
    

The second if statement from Prog41 has been replaced by an else clause which, as you might expect, is only executed if the conditional expression in the if part of the statement evaluates to false. Thus, in every case either the if part or the else part of the statement will be executed, depending on the value of the conditional expression. It is no longer possible for both of the statement parts to be executed. Note that the else clause is part of the if statement, and cannot occur by itself in the code.

There is a still more general form of the if-else statement, allowing several else clauses to be chained together. This has the general form:

if (<condition1>) {
  <body1>
}
else if (<condition2>) {
  <body2>
}
  .
  .
  .
else if (<conditionN>) {
  <bodyN>
}
else {
  <bodyN+1>
}
    

Where the ... represents any number of additional else-if clauses. The behaviour of such a statement should be reasonably obvious to you by now: body1 will be executed if and only if condition1 is true, body2 will be executed if and only if condition2 is true and so on, until we reach bodyN+1 which is executed only if none of the conditions is true. For example, the code used by our simple ATM to select a transaction type could be written as follows:

1  import java.io.*;
2  
3  class Prog43 {
4  
5    // An example if-elseif-else statement
6    public static void main(String args[]) throws IOException {
7  
8      BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
9      String transaction;
10 
11     System.out.print("Enter transaction type: ");
12     transaction = in.readLine();
13 
14     if (transaction.equals("withdraw")) {
15       // Do withdrawal
16       System.out.println("withdrawal completed!");
17     }
18     else if (transaction.equals("deposit")) {
19       // Do deposit
20      System.out.println("deposit completed!");
21     }
22     else if (transaction.equals("balance")) {
23       // Get balance
24       System.out.println("balance completed!");
25     }
26     else {
27       // Unknown command
28       System.out.println("unknown transaction type!");
29     }
30   }
31 }
    

Prog43 is not terribly useful, but it does illustrate a typical use of chained else clauses, to select amongst a range of options that can be chosen by the user. You should also note the use of the equals method for comparing character strings in the conditional expressions in Prog43. Non-numeric types (such as strings) cannot in general be compared using the standard operators such as == or <, for reasons that are inherent to the design of the Java language and too complex to go into here. Instead, these types all provide an equals method that is equivalent to the == operator on numeric types. The notions of 'less than' and 'greater than' are not often applicable to non-numeric types, but when they are the type will usually provide methods to perform these comparisons as well. For instance, the String type provides a method compareTo that indicates whether one string is less than, equal to or greater than another, in the sense of an alphabetical ordering of the two strings.


Exercises

  1. How does the behaviour of Prog43 change if you use the == operator instead of the equals method in the conditional expressions?

  2. Write a program that will read two strings from the keyboard, then print the text "Snap!" if (and only if) the two entered strings contain exactly the same text.

  3. Write a program that will read two numbers x and y from the keyboard, then indicate whether x is less than, equal to or greater than y.

  4. What would be the output of the following code fragment if the value of x is 27 and the value of y is 42? If x is 12 and y is 9?

    if (x != y) {
      if (x < y) {
        if ((y - x) > 10) {
          System.out.println("less than");
        }
        else {
          System.out.println("close, but less than");
        }
      }
      else {
        if ((x - y) < 10) {
          System.out.println("close, but greater than");
        }
        else {
          System.out.println("greater than");
        }
      }
    }
    else {
      System.out.println("equal");
    }
    	

The switch statement

The switch statement can be viewed as an extremely specialised variation of the chained if-else construct presented above. It is designed to elegantly deal with one particular situation that occurs in a wide variety of programs: given a value of some type, determine which one of a set of recognised values it is, then perform some action depending on the value. The general form of a switch statement is shown below:

switch (<expression>) {
 case value1:
  <action>
 case value2:
  <action>
    .
    .
    .
 case valueN:
  <action>
 default:
  <action>
}
    

Where the ... represents any number of additional case/action pairs. The resemblance to an if statement should be clear: each case is equivalent to an if (<expression> == value) test in the if statement, with the corresponding actions equivalent to the body statements executed when that test evaluates to true. The action(s) under the default label have the same function as the final else clause in a chained if-else statement: they are executed if none of the preceeding case clauses matched the value of the expression. An example will help to make the similarities and differences between the two statements clear. The following two programs, one implemented using a switch statement and the other with an if, are exactly equivalent in terms of their behaviour (ignore the break statements in Prog44a for now, these will be explained shortly):

1  import java.io.*;
2  
3  class Prog44a {
4  
5    // Switch statement demonstration, part 1
6    public static void main(String[] args) throws IOException {
7  
8      BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
9      int i;
10 
11     System.out.print("Enter an integer: ");
12     i = Integer.parseInt(in.readLine());
13 
14     // The switch statement
15     switch (i) {
16      case 1:
17       System.out.println("one");
18       break;
19      case 2:
20       System.out.println("two");
21       break;
22      case 3:
23       System.out.println("three");
24       break;
25      default:
26       System.out.println("something else!");
27     }
28   }
29 }
    
1  import java.io.*;
2  
3  class Prog44b {
4  
5    // Switch statement demonstration, part 2
6    public static void main(String[] args) throws IOException {
7  
8      BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
9      int i;
10 
11     System.out.print("Enter an integer: ");
12     i = Integer.parseInt(in.readLine());
13 
14     // The if statement
15     if (i == 1) {
16       System.out.println("one");
17     }
18     else if (i == 2) {
19       System.out.println("two");
20     }
21     else if (i == 3) {
22       System.out.println("three");
23     }
24     else {
25       System.out.println("something else!");
26     }
27   }
28 }
    

There are some restrictions on the format of a switch statement, which reflect its history in the C and C++ languages as much as any inherent limitations of Java. First, and most importantly, the expression at the start of the statement must evaluate to a value of one of the following types: byte, char, short, int or long. You have seen most of these types already; together they constitute all of Java's 'primitive' integer types. The reasons for this restiction are quite obscure and rather pointless in an object-oriented, interpreted language like Java. Nevertheless, the designers chose to leave them in. Second, the values attached to the case clauses must be constant expressions. Essentially this means that the case values must be able to be computed when the program is compiled. Thus, values such as 2, 'a' or even 2+34 are acceptable, but any expression involving variables or function calls such as i+2 or square(6) will be rejected. You may want to experiment a little with Prog44a to determine exactly what is and is not allowed in a switch statement.

Earlier I promised to explain the meaning of the break statements in Prog44a. You have already seen the break statement used in loops to trigger an early exit from the body of the loop. The for purposes of break, the body of a switch statement is treated exactly like the body of a loop: when the program encounters the break it will immediately jump to the next statement following the switch body. Can you see what will happen if we don't include a break between the various cases of the switch? Prog45 illustrates this situation:

1  import java.io.*;
2  
3  class Prog45 {
4  
5    // Switch statement demonstration, part 3
6    public static void main(String[] args) throws IOException {
7  
8      BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
9      int i;
10 
11     System.out.print("Enter an integer: ");
12     i = Integer.parseInt(in.readLine());
13 
14     // The switch statement
15     switch (i) {
16      case 1:
17       System.out.println("one");
18      case 2:
19       System.out.println("two");
20      case 3:
21       System.out.println("three");
22      default:
23       System.out.println("something else!");
24     }
25   }
26 }
    

As you can see by running Prog45, with the break statements the program simply continues on until it reaches the end of the switch statement body, ignoring the case labels but executing the code for each case anyway! In most cases, then, the break statements are essential to ensure that the program only executes the code associated with the particular case that was matched. There are however some situations where this behaviour of 'falling through' into the following cases can be useful. For instance, if you want to perform the same action for more than one case, as shown in Prog46 below:

1  import java.io.*;
2  
3  class Prog46 {
4  
5    // Switch statement demonstration, part 4
6    public static void main(String[] args) throws IOException {
7  
8      BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
9      char c;
10 
11     System.out.print("Enter a character: ");
12     c = (char)in.read();
13 
14     // The switch statement
15     switch (c) {
16      case 'a':
17      case 'b':
18      case 'c':
19       System.out.println("You typed 'a' or 'b' or 'c'");
20       break;
21      default:
22       System.out.println("You typed something else!");
23     }
24   }
25 }
    

In this program, the action sections for the 'a' and 'b' cases are empty, so these will simply fall through to the 'c' case whenever an 'a' or a 'b' is entered. Without the fall through behaviour, we would have had to duplicate line 19 for each of the three cases. Even stranger situations exist, where it is useful for the earlier cases to actually carry out some actions before falling through. Careful ordering of the case labels is necessary to make full use of this 'feature'!


Exercises

  1. Convert the following if statement to an equivalent switch statement:

    if (i > 0) {
      if (i <= 5) {
        System.out.println("Group A");
      }
      else if (i <= 10) {
        System.out.println("Group B");
      }
      else if (i <= 15) {
        System.out.println("Group C");
      }
      else {
        System.out.println("Group D");
    }
    else {
      System.out.println("Group D");
    }
    	
  2. Convert the following switch statement to an equivalent if statement:

    switch (c) {
     case 'a':
     case 'A':
      System.out.println("Option A selected");
      break;
     case 'b':
     case 'B':
      System.out.println("Option B selected");
      break;
     case 'c':
     case 'C':
      System.out.println("Option C selected");
      break;
     default:
      System.out.println("Unknown option selected");
    }
    	
  3. Write a program, using an if statement, that will 1) read a single character c from System.in and 2) indicate whether the c is a vowel, a consonant, a decimal digit or some other type of character.

  4. Repeat the previous question, but this time use a switch instead of an if statement.


Written by Scott Mitchell. Updated by Matthew Huntbach

These notes were produced as part of the course Introduction to Programming as it was given in the Department of Computer Science at Queen Mary, University of London during the academic years 1998-2001.

Last modified: 30 September 1999