Lab 7: Selective Behavior


Introduction

One of the easiest ways to make a program user-friendly is to make it menu-driven. That is, rather than prompting the user in some vague sort of way, we present them with a menu of the choices available to them. Then all the user has to do is look at the menu and choose one of the choices. Since the menu always tells the user what their options are, the user needs no special knowledge, making such a program easy to use.

For example, a simple 4-function calculator program might prompt the user by providing the following menu:

   Please enter:
      + to add two numbers;
      - to subtract two numbers;
      * to multiple two numbers; or
      / to divide two numbers.
   -->

Thanks to the menu, a user knows exactly what they are to enter, rather than having to guess.

Today's exercise is to complete such a program, and at the same time learn about some of the Java control structures (statements that control the flow of execution through your program).

 

Getting Started

Create a Calculate project in which to store today's work. Then save a copy of Calculate.java and add it to your project. Also add the packages ann and hoj to your project. Open the file and personalize its opening documentation. Then take a few moments to study its structure. Note the use of Assertion.check() to check the preconditions of our program. Make sure you understand the purpose of each statement in Calculate.java before you continue.

Ignoring its error-checking code, Calculate.java prescribes the following behavior:

   Our program should display on the screen a greeting, followed 
   by a menu of permissible operations.  It should then read a 
   user-specified operation from the keyboard.  It should then 
   prompt the user for the (two) operands for that operation,
   and then read those operands from the keyboard.  It should 
   then compute the result of applying the user-specified operation
   to the two operands.  It should conclude by displaying that
   result on the screen, with appropriate labeling.

except for the portion given in bold. Since there is no predefined Java capability to directly perform that operation, we will write method apply() to do so. Since this method will be closely tied to our calculator problem (and thus not particularly reusable), we will store it in Calculate.java, rather than in a separately compiled module.

 

Method Design

As usual, we use object-centered design to develop this method.

Behavior. From a "high level", our method must apply operation to op1 and op2 and return the result. If we break this task down into its "low level" details, we can describe the needed behavior as follows:

   Our method should receive from its caller an operation,
   and two operands.  If the operation is '+, our method 
   should return the sum of the two operands.  If the operation 
   is '-', our method should return their difference. If the 
   operation is '*', our method should return their product 
   of the two operands.  If the operation is '/', our method 
   should return their quotient.

Objects. From this behavioral description, we can identify the following objects:

Description

Type

Kind

Movement

Name

The operation

char

varying

received

operation

One of the operands

double

varying

received

op1

The other operand

double

varying

received

op2

The sum of the operands

double

varying

local

op1 + op2

The difference of the operands

double

varying

local

op1 - op2

The product of the operands

double

varying

local

op1 * op2

The quotient of the operands

double

varying

local

op1 / op2

From this list, we can specify the behavior of our method as follows:

   Receive: operation, a char;
            op1 and op2, two double values.
   Return: the (double) result of applying operation 
            to op1 and op2.

Using this specification, add a stub for a method named apply() below the main function in Calculate.java.

Method Operations From our behavioral description, we have these operations:

Description

Defined?

Name

Module?

1

Receive operation

yes

method call
mechanism

built-in

2

Receive op1

yes

method call
mechanism

built-in

3

Receive op2

yes

method call
mechanism

built-in

4

Return the sum of op1 and op2

yes

return, +

built-in

5

Return the difference of op1 and op2

yes

return, -

built-in

6

Return the product of op1 and op2

yes

return, *

built-in

7

Return the quotient of op1 and op2

yes

return, /

built-in

8

Do exactly one of 4-7, depending on operation

yes

??

built-in

As indicated, Java provides facilities for performing each of these operations. Operation #8 is different from the others, in that it requires selective behavior. One way to elicit selective behavior is by using the Java if statement. Such behavior can also be elicited by using the Java switch statement. We will compare and contrast these statements in the rest of this exercise.

Method Algorithm. We can organize these operations into the following algorithm:

   0. Receive operation, op1 and op2.
   1. If operation is '+':
         Return op1 + op2.
      Otherwise, if operation is '-':
         Return op1 - op2.
      Otherwise, if operation is '*':
         Return op1 * op2.
      Otherwise, if operation is '/':
         Return op1 / op2.
      Otherwise,
         Return a value indicating an error occurred.
      End if.

Here, we use a pseudo code form of the if statement that has multiple branches. The trick is to see how to code such a form in Java.

 

Coding 1: Using the if Statement

If you have correctly declared parameters for operation, op1 and op2 in your prototype and stub, step 0 of our algorithm should be taken care of. That just leaves step 1. As we suggested earlier, each of the "Return ..." parts of step 1 can be performed using a return statement that returns an appropriate expression. For example, we can perform the first and last "Return ..." parts with

   return op1 + op2;
   // other returns
   return Double.NaN;
Note that the expression Double.NaN retrieves the NaN (not-a-number) member of Java's Double class.

Add return statements for each of the other parts.

Our remaining problem is how to select the appropriate one of these return statements. One way to elicit selective behavior is by using the Java if statement, whose general pattern is:

   if ( Condition ) Statement1 [ else Statement2 ]

Here, if and else are Java keywords, Condition is a Java boolean expression, and Statement1 and Statement2 are either individual or compound Java statements (groups of statements in { } braces). The brackets in the pattern ([ and ]) are used to indicate that the else Statement2 can be omitted. Note that nothing prevents either Statement1 or Statement2 from being another if statement.

This pattern generates three different forms of the if statement. If the else Statement2 is omitted, we get the first form:

   if ( Condition )
      Statement

which is sometimes called a simple or single-branch if, since it allows the selective execution of a single section of code.

By contrast, an if statement that has an else Statement2 portion:

   if ( Condition )
      Statement1
   else
      Statement2

is sometimes called a two-branch if, since it allows the selective execution of either of two sections of code.

A third form of the statement occurs when Statement2 is another if statement:

   if ( Condition1 )
      Statement1
   else if  ( Condition2 )
      Statement2
	...
   else if ( ConditionN )
      StatementN
   else
      StatementN+1

This form is called a multi-branch if, since it allows the selection of any of N+1 different sections of code. When execution reaches such a statement, Condition1 is evaluated, and if it is true, Statement1 is executed while the remaining statements are skipped. However, if Condition1 is false, control proceeds to the else part where Condition2 is evaluated, and if it is true, Statement2 is executed while the remaining statements are skipped. This behavior continues until either a true condition is found, or the final condition is determined to be false, in which case StatementN+1 is executed.

It should be evident that this multi-branch if provides exactly the behavior we need to perform step 1 of our algorithm:

   1. If operation is '+':
         Return op1 + op2.
      Otherwise, if operation is '-':
         Return op1 - op2.
      Otherwise, if operation is '*':
         Return op1 * op2.
      Otherwise, if operation is '/':
         Return op1 / op2.
      Otherwise,
         Return Double.NaN.
      End if.

Add a multi-branch if to your apply() stub to encode this step.

Testing and Debugging. When you are done, execute your program and test its correctness. When your program is correct, save Calculate.java. Then, since we will be altering this program, save a copy of this first version under the name Calculate1.java, and print a hard copy of it. Then close Calculate1.java and reopen Calculate.java.

 

Coding 2: Using the switch Statement

It should be evident from your testing that the multi-branch if statement that you just wrote is functionally correct, in that it solves the problem. However, it suffers from a drawback:

In general, selecting Statementi using a multi-branch if statement requires the evaluation of i conditions. Since evaluation of each condition consumes time, statements that occur later in the multi-branch if statement take longer to execute than do statements that occur earlier.

In certain situations, this penalty can be avoided by using the Java switch statement, an alternative selective behavior statement. Its simplified general pattern is:

   switch ( Expression )
   {
      CaseList1 
         StatementList1
      CaseList2 
         StatementList2
      ...
      CaseListN 
         StatementListN
      default: 
         StatementListN+1
   }

where Expression is any Java expression that evaluates to a integer-compatible constant; each StatementListi is a sequence of valid Java statements, and each CaseListi is one or more Cases of the form:

   case ConstantOrLiteral :

where ConstantOrLiteral is an integer-compatible constant or literal.

When execution reaches a switch statement, the following actions occur:

  1. The Expression is evaluated.
  2. If its value is present in CaseListi, then execution begins in StatementListi and proceeds, until a break statement, a return statement, (a throw statement,) or the end of the switch statement is encountered.
  3. If the value of Expression is not present in any CaseListi, then the (optional) default StatementListN+1 is executed, if present.

Note that a given ConstantOrLiteral value can appear in only one CaseListi.

Note also that a break statement, whose pattern is

   break;

is usually used at the end of each StatementListi, to force execution to leave the switch statement. If a break (or return, or throw) is not provided, the Java switch statement has an interesting drop-through effect, so that upon reaching the end of StatementListi, execution proceeds to StatementListi+1. This can lead to very hard-to-find logic errors, if you forget about it, or are used to using the similar case statements of other languages.

An important question is: Under what circumstances should a switch statement be used, instead of an if statement?

The answer is that if an algorithm requires selective execution using multi-branch logic like the following:

   If (Variable  is equal to Value1) then
      StatementList1
   Else if (Variable is equal to  Value2) then
      StatementList2
   ...
   Else if (Variable is equal to ValueN) then
      StatementListN
   Else
      StatementListN+1
   End if.

then the algorithm can be encoded using a multi-branch if statement:

   if (Variable == Value1)
   {
      StatementList1
   }
   else if (Variable == Value2)
   {
      StatementList2
   }
   ...
   else if (Variable == ValueN)
   {
      StatementListN
   }
   else
   {
      StatementListN+1
   }

but it is usually more efficient to use a switch statement with the pattern:

   switch (Variable) 
   {
      case Value1:
         StatementList1
         break;
      case Value2:
         StatementList2
         break;
      ...
      case ValueN:
         StatementListN
         break;
      default:
         StatementListN+1
   }
Note that in order to use the switch instead of the if, each Valuei must be an integer-compatible constant or literal.

The reason the switch statement solution is more efficient than the multi-branch if statement in such situations is that where execution of StatementListi in a multi-branch if requires the evaluation of i conditions, a switch statement can select StatementListi in approximately the time it takes to evaluate one condition. The switch statement thus eliminates the nonuniform execution time of statements controlled by a multi-branch if statement.

However, note the restrictions:

Using the pattern above, replace the multi-branch if statement in the method apply() with a functionally equivalent (but more efficient) switch statement.

Testing and Debugging. Translate and test what you have written. When it is correct, save your work and print a hard copy of Calculate.java.

 

Phrases you should now understand:

Condition, Boolean Expression, Relational Operator, Logical Operator, Selective Execution, If Statement, Switch Statement.


 

Submit:

Hard copies of Calculate.java and Calculate1.java, plus an execution record for each.


Back to This Lab's Table of Contents

Back to the Prelab Questions

Forward to the Homework Projects


Back to the Table of Contents

Back to the Introduction


Copyright 2000 by Prentice Hall. All rights reserved.