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
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)) { ... }
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.
How does the behaviour of Prog43
change if you use the
==
operator instead of the equals
method
in the conditional expressions?
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.
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
.
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 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'!
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"); }
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"); }
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.
Repeat the previous question, but this time use a
switch
instead of an if
statement.
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