Hands On Java Lab 5 Exercise Lab 5: Controlling Method Behavior Introduction Today's lab explores some Java statements that we can use to write more sophisticated methods. The exercise consists of two parts: In the first part, we examine a problem whose solution requires more complicated behaviors than we have seen thus far, and introduce the new statements to elicit that behavior. In the second part, we introduce a new program-development tool called the debugger and use it to study the behavior of the new statements. Part I: The Tax Computation Problem Our problem today is to write an interactive tax computation program, that the owner of small online business might use to compute the total cost of items being sold to buyers in states with different sales tax. To make the problem more interesting, we will assume that a national luxury tax has been enacted. The one percent luxury tax will be levied against any purchase over one hundred dollars. Recall that object-centered design involves several stages: Identify the behavior we wish the program to exhibit in order to solve the problem. Identify the objects in that behavior. Identify the operations in that behavior. Organize the operations and objects into an algorithm. Now that we know about methods and classes, we must incorporate them into our design scheme. Methods can be thought of as the means of creating our own operations, so the natural place to incorporate them is in step 3: Identify the behavior we wish the program to exhibit in order to solve the problem. Identify the objects in that behavior. Identify the operations in that behavior. If there is no predefined way to perform an operation, build a method to perform it, and store it in the class responsible for providing it. Organize the operations and objects into an algorithm. Once we have our design, we can encode it in a programming language, test and debug the resulting program, and then perform any maintenance required over its lifetime. Today's exercise is to use these stages to develop a program that solves our problem. Preparing Your Workspace Begin by creating a project directory for this exercise (e.g., lab5) in which to store today's work and then save copies of the files TaxTotals.java, and Tax.java in your project directory. If you are using an integrated development environment (IDE) like CodeWarrior, create a new project (e.g., TaxTotals) in your project directory, and add TaxTotals.java, Tax.java, and the ann package to it. Then open TaxTotals.java, add it to the project, and take a moment to personalize its opening documentation. Design As always, spending a bit of time planning how to attack our problem will result in a better solution. To do so, we follow the steps of object-centered design. Behavior. We can begin by visualizing and writing down how we want our program to behave. One approach is to have our program behave something like the following:
This program computes total costs interactively.
To begin, enter the number of items: 3
Enter the item name, cost and tax rate for item 1: kleenex 25 5.25
kleenex 26.3125
Enter the item name, cost and tax rate for item 2: eggs 1.50 6.15
eggs 1.59225
Enter the item name, cost and tax rate for item 3: tires 350 6.15
tires 374.375
Put into words, our program should
display on the screen a greeting, followed by a prompt for the
number of items, which it should then read from the keyboard.
For each item, our program should then display a prompt for
the item name, cost and tax rate. The program should then
read these values from the keyboard, and compute and display
the total item cost, along with the name.
Objects. If we identify the nouns in this behavioral description, we get the following list: Description Type Kind Name The screen Screen varying theScreen A greeting String constant -- A prompt for input String constant -- The number of items int varying numberOfItems The keyboard Keyboard varying theKeyboard An item's name String varying name An item's cost double varying cost An item's tax rate double varying rate An item's pay double varying totalCost From this list, we can build a precise specification of how our program is to behave:
Input: The number of items;
Each item's name, cost, and tax rate.
Output: Each item's name and total cost.
Use this information to complete the specification of TaxTotals.java. Operations. If we identify the operations in our behavioral description, we get this list: Description Predefined? Name Library? display a String (greeting, prompts, labels) yes print ann.easyio Read an int (numberOfItems) from the keyboard yes readInt ann.easyio read a String (name) from the keyboard yes readWord ann.easyio read a double (cost, rate) from the keyboard yes readDouble ann.easyio compute an item's totalCost, given the cost and rate from the keyboard no ?? ?? display a double (totalCost) on the screen yes print ann.easyio repeat operations 3-6 once for each item yes for -- As indicated, most of these operations are provided for us by the ann.easyio package. Operations 1-4 and 6 should be familiar by now. There is no predefined Java capability to perform operation 5, and since accounting for luxury tax makes it nontrivial, we will write a method to perform this operation. Since it seems like an operation that might be useful again some day, we will store it in a separate class. Finally, operation 7 involves a new Java statement that we haven't seen before called the for statement, which provides a convenient way to repeat a group of statements a predetermined number of times. Algorithm. Even without knowing the details of how we will compute the total cost, we can organize our operations into an algorithm for our problem: Via theScreen, display a greeting on the screen, plus a prompt for the number of items. From theKeyboard, read an integer, storing it in numberOfItems. For each value itemNum in the range 1 to numberOfItems: Via theScreen, display a prompt for the name, cost and tax rate of item itemNum. From theKeyboard, read a String, a double and a double, storing them in name, cost and rate. Compute totalCost, using cost and rate. Via theScreen, display name and totalCost. End loop. Coding, Testing and Debugging Once we have designed an algorithm to solve our problem, we must encode that algorithm in the a high level programming language (i.e., Java). We can begin this process by constructing a minimal Java program, consisting of
public class TaxTotals
{
public static void main(String args[])
{
}
}
(These lines are already present in TaxTotals.java, after its opening documentation.) To make sure that this much is correct before we add to it, take a moment to compile TaxTotals.java. Given the minimal Java program, we are ready to encode our algorithm using stepwise translation. We therefore begin with the first step: 1. Via theScreen, display a greeting on the screen, plus a prompt for the number of items. Coding: This step can be performed using a Java output statement, which we have seen before:
theScreen.print( Value1 + Value2 + ... + ValueN );
so add an output statement to TaxTotals.java that displays the following message:
This program computes total costs interactively.
To begin, enter the number of items:
Don't forget to import the package ann.easyio.*! Before proceeding to the next step, check the correctness of what you just wrote by recompiling your program. The compiler will alert you to any syntax errors in your statement. If an error is listed, you can infer that the error(s) lies in the text you just added, since the program was error-free before that. Find your error(s) within those lines and correct them. When your source program compiles correctly, execute TaxTotals to test that it displays the intended message. If not, the statements you have added contain logic errors. (i.e., the statements you have added are syntactically correct, but they don't accomplish their task correctly.) Compare your program's statements against the output produced by TaxTotals and modify them as needed. When your program is error-free, proceed to the next step of our algorithm. 2. From theKeyboard, read an integer, storing it in numberOfItems. Coding: We can encode this step in Java using an input statement:
Var1 = theKeyboard.readInt();
Add an input statement to your source program to perform step 1. Don't forget to declare a variable to store numberOfItems! Check that what you have added is free of syntax errors before continuing. Note that we could use an Assertion.check() at this point to check that numberOfItems is nonnegative. However, doing so is unnecessary, thanks to our next step. 3. For each value itemNum in the range 1 to numberOfItems: a. Via theScreen, display a prompt for the name, cost and tax rate of item itemNum. b. From theKeyboard, read a String, a double and a double, storing them in name, cost and rate. c. Compute totalCost, using cost and rate. d. Via theScreen, display name and totalCost. End loop. Coding: Let's take this step a piece at a time, starting with the outer part (3) and then doing the inner parts (a-d). 3. For each value itemNum in the range 1 to numberOfItems: ... End loop. The purpose of step 3 is to count from 1 to the number of items, and repeat steps a-d that many times. That is, if there are three items, then steps a-d should be repeated three times. For situations like this that require repetitive behavior, Java supplies the for statement. The for statement can use its own local variable, called a loop-control variable, to do the counting. A simplified general form of a for statement that counts from firstValue to lastValue is:
for (Type loopVar = firstValue; loopVar <= lastValue; loopVar++)
{
Statements
}
where loopVar is the loop-control variable, and the Statements between the curley braces are called the body of the loop. The behavior of this statement is as follows: loopVar is declared and initialized to firstValue. loopVar is compared against lastValue. If the comparison evaluates to true: Statements get executed. loopVar++ is executed. Go to 2. Otherwise, control proceeds to the next statement. In our problem, we must count from 1 to the value stored in numberOfItems, using itemNum as the name of the loop-control variable. To do so, we can write:
for (int itemNum = 1; itemNum <= numberOfItems; itemNum++)
{
}
leaving its Statements empty for the moment. Note that if the user enters a negative value for numberOfItems, the body of the loop will not be executed, because the loop's body is only executed if the condition controlling the loop evaluates to true, and a negative value for numberOfItems will make this condition false. Add this to TaxTotals.java and then check its syntax. When the compiler generates no errors, proceed to steps 3a-3d. 3a. Via theScreen, display a prompt for the name, cost and tax rate of item itemNum. This is a normal output statement, like those we have seen before, except that in addition to displaying a string, it displays the value of our loop-control variable to generate an "item number." That is, if numberOfItems is 3, then our loop will execute three times, so add an output statement to the body of the loop that will generate the following:
Enter the name, cost and tax rate for item 1:
Enter the name, cost and tax rate for item 2:
Enter the name, cost and tax rate for item 3:
Then check the syntax of what you have written using the compiler. When your program translates correctly, run it and enter 3 for the number of items, to verify that what you have written is free of logic errors. 3b. From theKeyboard, read a String, a double and a double, storing them in name, cost and rate. This step can be encoded by adding a normal input statement to the body of the loop, after the output statement, so take a moment to do so. However, before such a statement will compile correctly, the objects name, cost and rate must be declared. This raises an interesting question: Where should these objects be declared? When a variable is declared within a block (i.e., a { and } pair), it does not exist outside of that block. Thus, if our algorithm required us to use name, cost and rate outside of the body of the loop, then we would have to declare them prior to the loop in order to use them both within and following the loop. However this is not the case; our algorithm uses name, cost and rate only within the body of the loop, so we can declare them just prior to their first use, within the body of the loop. Add the necessary statements to your program to (i) declare name, cost and rate, and (ii) fill these objects with values entered from the keyboard (inside the loop). Then check your code's syntax using the compiler, and continue when it is correct. Anytime we program an input operation, we should consider the possibility of human error: what could the user do to foul up our program? There are several possibilities here: The user could enter a negative value for cost. The user could enter a negative value for rate. The user could also enter a value for rate greater than the maximum tax rate; however our problem does not specify a maximum tax rate, so we will ignore that potential source of error for the moment. Since either of these errors will cause our program to generate incorrect pay amounts, our code should safeguard against them. To do so, add an Assertion.check() to the program that only allows the program to continue if cost is nonnegative and rate is nonnegative. Use a single Assertion.check() call to do so. (Don't forget its import directive!) Then check the syntax of what you have written using the compiler, then run your program and test that your Assertion.check() halts the program if either of these two errors occur. When it correctly "catches" these errors, continue. 3c. Compute totalCost, using cost and rate. This operation is not predefined, and so we will design and build a method to perform it. Method Analysis. To compute totalCost for an arbitrary item, our method needs the cost and rate. Since these values will differ from item to item, our method should receive these values from its caller. Method Behavior. Our method should receive the cost and rate from its caller. If cost is less than or equal to the minimum cost for the luxury tax, then our method should compute the total cost as cost plus cost times rate divided by 100 (assume that the rate is given as a percent). Otherwise, it should compute the regular cost as before; the luxury surcharge as one percent of the luxury base (cost minus the minimum for the luxury tax); and then compute the total cost as the sum of the regular cost and the luxury surcharge. Method Objects. Ignoring nouns like "our method" and "caller", we can identify the following objects in this description: Description Type Kind Movement Name item cost double varying received cost tax rate (percent) double varying received rate minimum luxury cost double constant local MIN_LUXURY total cost double varying returned totalCost regular cost double varying local regularCost luxury surcharge double varying local luxurySurcharge luxury base double varying local cost - MIN_LUXURY luxury tax rate double constant local LUXURY_RATE For each received object, we must provide a parameter to store that value. We can thus specify the behavior of our method as follows:
Receive: cost and rate, both doubles.
Precondition: 0 <= cost and 0 <= rate.
Return: the total cost, a double.
These observations allow us to create the following stub for our method:
public static double computeTotalCost(double cost, double rate)
{
}
Each object that is neither received nor returned must be defined as a local object, within the body of our method. For example, MIN_LUXURY will be a local constant double object, defined to have the value 100.0; while regularCost will be a local variable double object. Since this seems like a method that might be reusable some day, we will store its definition in a class named Tax. Open Tax.java. (If you are using an IDE, add Tax.javato the project.) Create the class Tax and inside that class insert the stub. Since other programs besides TaxTotals.java may be calling this method (and not checking the precondition ahead of time), make the first line in the method an Assertion.check() call that checks the method's precondition. (Copy-and-paste your Assertion.check () from TaxTotals.java.) Then add the necessary documentation to Tax.java Method Operations. From our behavioral description, we have these operations: Description Defined? Name Module? 1 receive cost and rate from caller yes method call mechanism built-in 2 compare cost and MIN_LUXURY in less-than-or-equal relationship yes <= built-in 3 compute totalCost normally (cost plus cost times rate divided by 100) yes * built-in 4 compute totalCost for luxury item 4a compute regualarCost yes * built-in 4b compute luxuryBase (cost minus MIN_LUXURY) yes - built-in 4c compute luxurySurcharge (luxuryBase times LUXURY_RATE divided by 100) yes *, / builtin 4d compute totalCost (regularCost plus luxurySurcharge) yes + built-in 5 if (2) is true, perform (3), otherwise perform (4) yes if built-in 6 return totalCost yes return built-in We have seen each of these operations before, except for operation (5), which introduces a new behavior in which we must select one group of statements or another, but not both. As we shall see shortly, the Java if statement provides this behavior. Method Algorithm. We can organize our objects and operations as follows: Receive cost and rate from the caller. If cost <= MIN_LUXURY Compute totalCost = cost + cost * rate/100. Otherwise a. Compute regularCost = cost + cost * rate/100.. b. Compute luxuryBase = cost - MIN_LUXURY. c. Compute luxurySurcharge = luxuryBase * LUXURY_RATE/100. d. Compute toalCost = regularCost + luxurySurcharge. Return totalCost. Given an algorithm, we are ready to encode our method. Method Coding. To encode this algorithm in our computeTotalCost() stub, we must know the syntax of the Java if statement. The general pattern is as follows:
if (BooleanExpression)
{
Statements1
}
else
{
Statements2
}
Here BooleanExpression is any expression that evaluates to true or false, and Statements1 and Statements2 are 1 or more Java statements. Statements1 is sometimes called the true section of the if, and Statements2 is called its false section. The reason for these names is that when execution reaches an if statement, its BooleanExpression is evaluated. If it is true, then the statements in Statements1 run, and those in Statements2 are skipped. Otherwise, the opposite occurs -- the statements in Statements1 are skipped, and those in Statements2 run. For our problem, we want BooleanExpression to perform the comparison described in operation (2). In the stub for computeTotalCost(), we can add the following if statement that compares cost and MIN_LUXURY using the less-than-or-equal relationship:
if (cost <= MIN_LUXURY)
{
}
else
{
}
As a parameter, object cost is defined for us, but MIN_LUXURY and LUXURY_RATE are not parameters, and so must be declared. Add statements at the beginning of the method that declare MIN_LUXURY as a constant whose value is 100.0, and declare LUXURY_RATE as a constant whose value is 5.0. To check this much, compile Tax.java, and you should just get the one error for not providing any return value. Continue if this is the case, otherwise fix the other errors. Coding operations (3) and (4) is straightforward, so add statements to the if's true section to perform (3), and add statements to its false section to perform (4). Note that each section of the if computes totalCost, however the true section uses the formula appropriate for no luxury tax, while the false section uses the formula appropriate when there is luxury tax. Since each section uses totalCost, it must be declared prior to both sections (i.e., before the if statement). Finish the method by adding a return statement that returns the value of totalCost. Then recheck the correctness of computeTotalCost(), continuing when it is free of syntax errors. Now that we have a syntactically correct computeTotalCost() method to perform step 3c of our program's algorithm, we are ready to code that step by calling our method. In the for loop within TaxTotals.java, add an assignment statement with a call to computeTotalCost(). Don't forget to pass it cost and rate as arguments! To check the correctness of our call, compile TaxTotals.java which will create TaxTotals.class and link it with the class file of Tax.java. When what you have written is free of syntax errors, continue to the final step of our algorithm. 3d. Via theScreen, display name and totalCost. This step is easily implemented using an output statement. Take a few moments and add an output statement so that if item milk had a total cost of $1.75, the following is displayed:
milk cost with tax is $150.75
Use whatever symbol is appropriate for your country's currency. Testing When you are finished, recompile the program. Then test it a few times using sample data, to check that it computes correct results. Maintenance Recall that program maintenance is the final stage of program development, in which modifications and improvements are added during its lifetime. Some studies have shown that the cost to write a program is only 20% of its total cost -- the rest is consumed by maintenance! One of the goals of object-oriented programming is to reduce this maintenance cost, by writing code that is reusable. To simulate program maintenance, we can improve TaxTotals by modifying it so that it displays totalCost in monetary format (i.e., with 2 decimal digits instead of the default number). The number of decimal digits in a real number is called the precision of that number. To show only two decimal digits, we must alter the default precision. As we have seen before, this can be done with an method in the ann.easyio package
printFormatted(DoubleExpression, IntegerExpression )
Modify your source program so that pay is displayed with precision 2. Then test the correctness of your modification. Part II: Using the Debugger While the compiler will inform us of any syntax errors our program contains, it cannot detect logic errors. The simplest way to check for logic errors is to examine the values of the variables in our program as the program executes, and verify that they are what they are supposed to be. Most environments provide a special tool called a debugger. A debugger allows you to execute a program statement-by-statement, and examine the values of variables (or expressions) as the program runs. There are certain common features that most debuggers have. Breakpoints When there is a problem with a program, you often have some idea of where the problem lies. You want to run our program until you get to a point close to the suspected trouble spot. By setting a breakpoint, you inform the debugger that it should stop executing the program at a certain point. Examining Object values Once you have temporarily halted the execution of our program, you would like to be able to examine the values that are stored in all of your variables and check to see if they match what you expect them to be. Single Step Suppose that you haven't found the bug in your program. You would like to be able to execute lines of code one at a time until you find the problem. Step Into, Over, Out Because modern methods are composed of many methods, you want to have the ability to decide whether or not to explore a method. If you trust a method, you will want to step over the method call. If you are not sure of a method, you will want to step into the method. Finally, if you are in a method and believe it to be okay, you can step out of the method and return to where it was called from. Depending on which environment you will be using click on the appropriate link for specialized instruction on the use of the associated debugger: SDK Code Warrior JBuilder From this exercise, you should now know the basics of using the debugger, as well as have a better understanding of how if statements and for loops work. Phrases you should now understand: Object-Centered Design, Algorithm, Coding, Testing, Debugging, Stepwise Translation, Selective Execution, If Statement, Repetitive Execution, For Statement. Submit: Hard copies of your final version of TaxTotals.java and Tax.java; plus a script file showing the execution of TaxTotals. 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.