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
valuesshould not contain negative values, but nothing is done to ensure that is the case. Hence,valuesmight 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.lengthmight be 0.
valuemight 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
NullPointerExceptionwill be thrown by the firstforloop intoPercentages(), becausesaleswas not initialized. Hence, both it andvalueswill benull, causing aNullPointerExceptionto 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
forloop in thetoPercentages()method so that it throws anIllegalArgumentExceptionif any element ofvaluesis 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
elseclause in theifstatement?Answer
Because the
throwstatement returns control to the caller (in essentially the same way as areturnstatement, 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
throwstatement returns control to the caller, only oneIllegalArgumentExceptionwill 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
forloop, but before the statement that allocates memory forresult, that throws anIllegalArgumentExceptioniftotalis 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
resultwhere 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
resultuntil 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
denominatorto 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,
denominatorholds 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-catchstatement to this application. Specifically, put only the statement that generated the exception inside of thetryblock and put no statements in thecatchblock. (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
ratiois initialized in thetryblock 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 lineWill this version of the application compile? Why or why not?
Answer
Yes. There still may be situations in which
ratiois 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
catchblock, add a call to theprintStackTrace()method of theArithmeticExceptionobjectae. What is in thecatchblock 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
ArithmeticExceptionis 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, passingsalesin 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
iis declared in theforstatement and, so, is not recognized outside of it. -
Declare
i(but do not initialize it) inside of thetryblock, just before theforloop 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
iis now declared in thetryblock so is not be recognized in thecatchblock. -
Move the declaration of
ito the same statement that declaresratio(but leave the initialization in theforstatement). 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
ito be used before it is initialized. Why is this error messaged generated anyway? (Hint: Think about block statements.)Answer
The
tryblock is treated as a single (block) statement. From the compiler's perspective, this statement may throw an exception causing thecatchblock to be entered before the statement is completed. -
Initialize
ibefore thetryblock (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
tryblock and enters thecatchblock. Control then leaves thecatchblock and then leavesmainwhich causes the application to terminate. -
Fix
Part3so that it executes properly. (Hint: Move thetry-catchblock inside of theforblock.) 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
Part4so that it loops "properly" and does not need to use atry-catchstatement. (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)NullPointerExceptions. Is this the best way to handle this situation?Answer
Probably not. The caller should know better than to pass a
nullarray into a method like this. So, specifying theNullPointerExceptiondoesn'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 anullparameter 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
nullhere becausedouble[]is a reference type. -
Is this an example of excessive
returnstatements?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
ifstatement with anelseclause 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
Stringinto component parts, called tokens. -
What are the three formal parameters of the explicit value constructor in the
StringTokenizerclass?Answer
The
Stringthat 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
NoSuchElementExceptionis 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
NumberFormatExceptionis thrown by the statementrightOperand = Double.parseDouble(rightString);.
Submission¶
Answer the questions in Canvas for your section's Lab 6 assignment based on this lab.