Lab 6: Exception Handling¶
Instructions
Answer the following questions one at a time. After answering each question, check your answer before proceeding to the next question.
Acknowledgment
This lab was written by Prof. David Bernstein. The original version is here.
Getting Started¶
Checkstyle is not required for this lab.
You can turn off Checkstyle warnings for this lab by editing your .vscode/Checkstyle.xml
file.
Go to line 146, and replace edu|exams
with lab06
:
value="[/\\](acts|lab06)[/\\]|([^/\\]+Test|Test[^/\\]+)\.java$"/>
Then download and unzip the Provided Code into your src/labs
folder, creating a new folder named lab06
.
Each source file corresponds to a separate part of the lab.
Part 1: Throwing and Re-throwing Exceptions¶
This part of the lab will help you better understand how to throw and re-throw exceptions.
-
Implement the following method (in
Part1.java
)./** * Calculates an array of percentages from an array of values. * * Specifically, this method calculates the total of all of the * elements in the given array and divides each element into the * total to determine the percentages. * * @param values The array of non-negative values * @return The array of percentages. */ public static double[] toPercentages(double[] values) { }
Answer
public static double[] toPercentages(double[] values) { double total; double[] result; // Calculate the total total = 0; for (int i=0; i<values.length; i++) { total += values[i]; } // Allocate memory for the result result = new double[values.length]; // Calculate the percentages for (int i=0; i<values.length; i++) { result[i] = values[i] / total; } return result; }
-
Desk check the
toPercentages()
method, looking for trigger conditions that will cause it to fail. What are the four obvious trigger conditions?Answer
- The comments say that
values
should not contain negative values, but nothing is done to ensure that is the case. Hence,values
might contain negative elements. - Whenever division is used, it is important to check for situations in which the divisor might be 0. In this case, there are two situations that might give rise to a divisor of 0.
- First, the total might be 0 (which, if the values are all non-negative, will only be true if all of the values are 0).
- Second,
values.length
might be 0.
value
might benull
.
- The comments say that
-
What exception will be thrown when the following application is executed? What line will cause the exception to be thrown? Why?
private static double[] sales; public static void main(String[] args) { double[] percentages; percentages = toPercentages(sales); }
Answer
A
NullPointerException
will be thrown by the firstfor
loop intoPercentages()
, becausesales
was not initialized. Hence, both it andvalues
will benull
, causing aNullPointerException
to be thrown when an attempt is made to accessvalues.length
. -
Modify the declaration of the
toPercentages()
method so that it specifies theNullPointerException
. What is the declaration now?Answer
public static double[] toPercentages(double[] values) throws NullPointerException
-
Modify the first
for
loop in thetoPercentages()
method so that it throws anIllegalArgumentException
if any element ofvalues
is negative. What statements are in the body of the loop now?Answer
if (values[i] < 0.0) { throw new IllegalArgumentException("Element " + i + "is < 0"); } total += values[i];
-
Why don't you need to include an
else
clause in theif
statement?Answer
Because the
throw
statement returns control to the caller (in essentially the same way as areturn
statement, though to a different place). -
What will happen if more than one element is negative? In particular, how many exceptions will be thrown?
Answer
Because the
throw
statement returns control to the caller, only oneIllegalArgumentException
will be thrown, when the first negative element is encountered. -
Modify the declaration of the
toPercentages()
method so that it now also includesIllegalArgumentException
. What is the declaration now?Answer
public static double[] toPercentages(double[] values) throws IllegalArgumentException, NullPointerException
-
Add a statement after the first
for
loop, but before the statement that allocates memory forresult
, that throws anIllegalArgumentException
iftotal
is 0. What statement did you add?Answer
if (total <= 0.0) { throw new IllegalArgumentException("Total is 0"); }
-
The
toPercentages(double[])
method is now implemented as follows.public static double[] toPercentages(double[] values) throws IllegalArgumentException, NullPointerException { double total; double[] result; // Calculate the total total = 0; for (int i=0; i<values.length; i++) // throws NullPointerException { if (values[i] < 0.0) { throw new IllegalArgumentException("Element " + i + " < 0"); } total += values[i]; } if (total <= 0.0) { throw new IllegalArgumentException("Total is 0"); } // Allocate memory for the result result = new double[values.length]; // Calculate the percentages for (int i=0; i<values.length; i++) { result[i] = values[i] / total; } return result; }
Why is the statement that allocates memory for
result
where it is rather than earlier (for example right after the declaration or even in the declaration statement)?Answer
Because there is no reason to allocate memory for
result
until after it is clear that it will be needed. Exceptions might be thrown in three different places, thereby returning control to the caller. The memory shouldn't be allocated until after that.
Part 2: Catching Exceptions¶
This part of the lab will help you better understand how to catch exceptions.
-
What is printed by the following application?
public class Part2 { public static void main(String[] args) { int denominator, numerator, ratio; numerator = 5; denominator = 2; ratio = numerator / denominator; System.out.println("The answer is: "+ratio); System.out.println("Done."); // Don't move this line } }
Answer
The answer is: 2 Done.
-
Change the value of
denominator
to 0. What runtime error message is now generated by this application?Answer
Exception in thread "main" java.lang.ArithmeticException: / by zero at Part2.main(Part2.java:11)
-
Why is this error message generated at run-time (rather than at compile-time)?
Answer
The compiler can not find defects that result from variables being assigned particular values (since the assignments do not take place until run time). In this case, at run-time,
denominator
holds the value 0 so the division operator throws anArithmeticException
. Since the exception is not caught, the application terminates and an error message is printed to the console. -
Add a
try-catch
statement to this application. Specifically, put only the statement that generated the exception inside of thetry
block and put no statements in thecatch
block. (Hint: You should be able to determine what exception to catch from the error message that you received during the previous step. You should also be able to determine the line number of the statement that generated the exception if you didn't change the white space.) What statements are now in the body of themain()
method?Answer
int denominator, numerator, ratio; numerator = 5; denominator = 0; try { ratio = numerator / denominator; } catch (ArithmeticException ae) { } System.out.println("The answer is: "+ratio); System.out.println("Done."); // Don't move this line
-
What error message is generated by compiling this version of the application?
Answer
Something like:
though the line number will vary depending on white space.Part2.java:17: variable ratio might not have been initialized System.out.println("The answer is: "+ratio); ^
-
Why is this compile-time error message generated?
Answer
This error message is generated because
ratio
is initialized in thetry
block and this initialization will not take place if an exception is thrown before ratio can be assigned a value. -
Modify the body of the main method as follows.
int denominator, numerator, ratio; numerator = 5; denominator = 0; try { ratio = numerator / denominator; System.out.println("The answer is: "+ratio); } catch (ArithmeticException ae) { System.out.println("Divide by 0."); } System.out.println("Done."); // Don't move this line
Will this version of the application compile? Why or why not?
Answer
Yes. There still may be situations in which
ratio
is not assigned a value, but it is only used in another statement in situations in which it is. -
What output is generated by this application?
Answer
Divide by 0. Done.
-
Does the application terminate in an orderly fashion?
Answer
Yes. Even though there is a division by 0, the application accounted for it and so terminated in an orderly fashion.
-
At the end of the
catch
block, add a call to theprintStackTrace()
method of theArithmeticException
objectae
. What is in thecatch
block now?Answer
catch (ArithmeticException ae) { System.out.println("Divide by 0."); ae.printStackTrace(); }
-
What output is generated by the application now?
Answer
Something like:
Divide by 0. java.lang.ArithmeticException: / by zero at Example.main(Part2.java:13) Done.
-
Does the application terminate in an orderly fashion?
Answer
Yes. An
ArithmeticException
is thrown when the division is attempted but it is caught. After it is caught some messages are printed and then the application terminates.The fact that the
printStackTrace()
method prints a message that looks like an error message does not mean that the application does not account for all possible situations and terminate properly. (Though it would, indeed, scare a user. Hence, this method should only be used when debugging.) -
Returning to the
toPercentages()
method in the previous section, write a fragment that callstoPercentages()
, passing it the arraysales
. IftoPercentages()
returns normally, then the fragment must print each element. On the other hand, if it returns "abnormally", then the fragment must either call a method namedreportInvalidSalesData()
or a method namedreportNonexistentSalesData()
, as appropriate, passingsales
in both cases. What statements did you add?Answer
try { percentages = toPercentages(sales); for (int i=0; i<percentages.length; i++) { System.out.println("Percentage " + i + ": " + percentages[i]); } } catch (IllegalArgumentException iae) { reportInvalidSalesData(sales); } catch (NullPointerException npe) { reportNonexistentSalesData(sales); }
Part 3: Scope Issues¶
This part of the lab considers an example of exception handling within and outside of block statements.
-
What error messages are generated by compiling the following application?
public class Part3 { public static void main(String[] args) { int ratio; int[] numbers = {100,10,0,5,2,8,0,30}; try { for (int i=0; i < numbers.length-1; i++) { ratio = numbers[i] / numbers[i+1]; System.out.println(numbers[i]+"/"+numbers[i+1]+"="+ratio); } } catch (ArithmeticException ae) { System.out.println("Couldn't calculate "+ numbers[i]+"/"+numbers[i+1]); } } }
Answer
Part3.java:19: error: cannot find symbol numbers[i]+"/"+numbers[i+1]); ^ symbol: variable i location: class Part3 Part3.java:19: error: cannot find symbol numbers[i]+"/"+numbers[i+1]); ^ symbol: variable i location: class Part3
-
Why are these compile-time error message generated? (Hint: Think about block statements and block scope.)
Answer
The variable
i
is declared in thefor
statement and, so, is not recognized outside of it. -
Declare
i
(but do not initialize it) inside of thetry
block, just before thefor
loop as follows.int i; for (i=0; i < numbers.length-1; i++)
What error message are generated by compiling the application now?
Answer
Part3.java:20: error: cannot find symbol numbers[i]+"/"+numbers[i+1]); ^ symbol: variable i location: class Part3 Part3.java:20: error: cannot find symbol numbers[i]+"/"+numbers[i+1]); ^ symbol: variable i location: class Part3 2 errors
-
Why are these compile-time error message generated?
Answer
The variable
i
is now declared in thetry
block so is not be recognized in thecatch
block. -
Move the declaration of
i
to the same statement that declaresratio
(but leave the initialization in thefor
statement). What error message is generated by compiling the application now?Answer
Part3.java:20: variable i might not have been initialized numbers[i]+"/"+numbers[i+1]); ^
-
It is not possible for
i
to be used before it is initialized. Why is this error messaged generated anyway? (Hint: Think about block statements.)Answer
The
try
block is treated as a single (block) statement. From the compiler's perspective, this statement may throw an exception causing thecatch
block to be entered before the statement is completed. -
Initialize
i
before thetry
block (so it will compile). What output is generated by this application now?Answer
100/10=10 Couldn't calculate 10/0
-
Why aren't all of the divisions even attempted?
Answer
During iteration 1 (not not be confused with iteration 0) of the loop an exception is thrown. When this happens control leaves the
try
block and enters thecatch
block. Control then leaves thecatch
block and then leavesmain
which causes the application to terminate. -
Fix
Part3
so that it executes properly. (Hint: Move thetry-catch
block inside of thefor
block.) What is the body of themain()
method now?Answer
int i, ratio; int[] numbers = {100,10,0,5,2,8,0,30}; for (i=0; i < numbers.length-1; i++) { try { ratio = numbers[i] / numbers[i+1]; System.out.println(numbers[i]+"/"+numbers[i+1]+"="+ratio); } catch (ArithmeticException ae) { System.out.println("Couldn't calculate "+ numbers[i]+"/"+numbers[i+1]); } }
Part 4: Inappropriate Uses of Exception Handling¶
This part of the lab considers inappropriate uses of exception handling and how to "fix" them.
-
What output is generated by the following application?
public class Part4 { public static void main(String[] args) { int i; int[] data = {50, 320, 97, 12, 2000}; try { for (i=0; i < 10; i++) { System.out.println(data[i]); } } catch (ArrayIndexOutOfBoundsException aioobe) { System.out.println("Done"); } } }
Answer
50 320 97 12 2000 Done
-
Modify
Part4
so that it loops "properly" and does not need to use atry-catch
statement. (Note: The output should not change.) What is the body of themain()
method now?Answer
int i; int[] data = {50, 320, 97, 12, 2000}; for (i=0; i < data.length; i++) { System.out.println(data[i]); } System.out.println("Done");
-
Earlier, you modified the
toPercentages()
method so that it specifies (i.e., re-throws)NullPointerException
s. Is this the best way to handle this situation?Answer
Probably not. The caller should know better than to pass a
null
array into a method like this. So, specifying theNullPointerException
doesn't really provide valuable information to the caller.In addition, the
toPercentages()
method can handle this situation by returning a special value. -
Modify the
toPercentages()
method so that it checks for anull
parameter but handles it with a special return value.Answer
public static double[] toPercentages(double[] values) throws IlegalArgumentException { double total; double[] result; if (values == null) { return null; } // Calculate the total total = 0; for (int i=0; i<values.length; i++) { if (values[i] < 0.0) { throw new IllegalArgumentException("Element " + i + " < 0"); } total += values[i]; } if (total <= 0.0) { throw new IllegalArgumentException("Total is 0"); } // Allocate memory for the result result = new double[values.length]; // Calculate the percentages for (int i=0; i<values.length; i++) { result[i] = values[i] / total; } return result; }
-
Why is it possible to return the special value that was used in the answer to the previous question?
Answer
We can return
null
here becausedouble[]
is a reference type. -
Is this an example of excessive
return
statements?Answer
Most people don't think so. This is often called "early return" and most people consider it appropriate way to handle situations in which inappropriate parameters are passed to a method. They argue that it is clearer than using an
if
statement with anelse
clause and that it reduces the amount of indenting in the "important part" of the method.
Part 5: Some Other Exceptions¶
This part of the lab will give you some experience with some other exceptions, where they arise, and how they can be used.
-
What functionality does a StringTokenizer object provide?
Answer
It can be used to break a
String
into component parts, called tokens. -
What are the three formal parameters of the explicit value constructor in the
StringTokenizer
class?Answer
The
String
that is going to be tokenized, the delimiters to use while tokenizing, and whether or not the delimiters should be returned as tokens. -
What output will be generated by the following application if it is passed a single command-line argument containing the
String
"5.3+9.2"?import java.util.*; public class Part5 { public static void main(String[] args) { double leftOperand, result, rightOperand; String leftString, operator, rightString; StringTokenizer tokenizer; tokenizer = new StringTokenizer(args[0], "+", true); try { leftString = tokenizer.nextToken(); operator = tokenizer.nextToken(); rightString = tokenizer.nextToken(); leftOperand = Double.parseDouble(leftString); rightOperand = Double.parseDouble(rightString); if (operator.equals("+")) result = leftOperand + rightOperand; else result = 0.0; System.out.println("Result: " + result); } catch (NoSuchElementException nsee) { System.out.println("Invalid syntax"); } catch (NumberFormatException nfe) { System.out.println("One or more operands is not a number"); } } }
Answer
Result: 14.5
-
What output will be generated by this application if it is passed a single command-line argument containing the
String
"5.3+"?Answer
Invalid syntax
-
Why? In particular, what exception is thrown and why?
Answer
A
NoSuchElementException
is thrown by the statementrightString = tokenizer.nextToken();
. -
What output will be generated by this application if it is passed a single command-line argument containing the
String
"5.3+a"?Answer
One or more operands is not a number
-
Why? In particular, what exception is thrown and why?
Answer
A
NumberFormatException
is thrown by the statementrightOperand = Double.parseDouble(rightString);
.
Submission¶
Answer the questions in Canvas for your section's Lab 6 assignment based on this lab.