Lab: Experimenting with Exception Handling
Instructions:
Answer the following questions one at a time. After answering each question,
check your answer (by clicking on the check-mark icon if it is available)
before proceeding to the next question.
1. Some Advice:
For this lab, you may want to both think about what will happen when
you compile and execute the applications, and actually compile and
execute them.
2. 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.
/**
* 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)
{
}
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?
1. 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.
2 and 3. 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.
4. value
might be null
.
-
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);
}
A NullPointerException
will be thown by the first
for
loop in toPercentages()
because
sales
was not initialized. Hence, both it
and values
will be null
,
causing a NullPointerException
to be thrown when an
attempt is made to access values.length
.
-
Modify the declaration of the
toPercentages()
method
so that it specifies the NullPointerException
.
What is the declaration now?
public static double[] toPercentages(double[] values) throws NullPointerException
-
Modify the first
for
loop in the
toPercentages()
method so that it throws
an IllegalArgumentException
if any element of
values
is negative.
What statements are in the body of the loop now?
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 the
if
statement?
Because the throw
statement returns control to the caller
(in essentially the same way as a return
statement, though
to a different place).
-
What will happen if more than one element is negative? In particular, how
many exceptions will be thrown?
Because the throw
statement returns control to the caller,
only one IllegalArgumentException
will be thrown, when the
first negative element is encountered.
-
Modify the declaration of the
toPercentages()
method
so that it now also includes IllegalArgumentException
.
What is the declaration now?
public static double[] toPercentages(double[] values)
throws IllegalArgumentException, NullPointerException
-
Add a statement after the first
for
loop, but before
the statement that allocates memory for result
,
that throws an IllegalArgumentException
if total
is 0.
What statement did you add?
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)?
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.
3. Catching Exceptions:
This part of the lab will help you better understand how to
catch exceptions.
-
What is printed by the following application?
Example1.java
public class Example1
{
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
}
}
-
Change the value of
denominator
to 0.
What runtime error message is now generated by this application?
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Example1.main(Example1.java:11)
-
Why is this error message generated at run-time (rather than at
compile-time)?
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 an
ArithmeticException
. 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 the try
block and put no statments in the
catch
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 statemen that generated the exception if you didn't
change the white space.)
What statements are now in the body of the main()
method?
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?
Something like:
Example.java:17: variable ratio might not have been initialized
System.out.println("The answer is: "+ratio);
^
though the line number will vary depending on white space.
-
Why is this compile-time error message generated?
This error message is generated because ratio
is
initialized in the try
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?
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?
-
Does the application terminate in an orderly fashion?
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 the
printStackTrace()
method of the
ArithmeticException
object ae
.
What is in the catch
block now?
catch (ArithmeticException ae)
{
System.out.println("Divide by 0.");
ae.printStackTrace();
}
-
What output is generated by the application now?
Something like:
Divide by 0.
java.lang.ArithmeticException: / by zero
at Example.main(Example.java:13)
Done.
-
Does the application terminate in an orderly fashion?
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
calls toPercentages()
, passing it the
array sales
. If toPercentages()
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 named reportInvalidSalesData()
or a method named reportNonexistentSalesData()
,
as appropriate, passing sales
in both cases.
What statements did you add?
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);
}
4. 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?
Example2.java
public class Example2
{
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]);
}
}
}
Example2.java:19: error: cannot find symbol
numbers[i]+"/"+numbers[i+1]);
^
symbol: variable i
location: class Example2
Example2.java:19: error: cannot find symbol
numbers[i]+"/"+numbers[i+1]);
^
symbol: variable i
location: class Example2
-
Why are these compile-time error message generated? (Hint: Think about
block statements and block scope.)
The variable i
is declared in the for
statement
and, so, is not recognized outside of it.
-
Declare
i
(but do not initialize it) inside of
the try
block, just before the for
loop
as follows.
int i;
for (i=0; i < numbers.length-1; i++)
What error message are generated by compiling the application now?
Example2.java:20: error: cannot find symbol
numbers[i]+"/"+numbers[i+1]);
^
symbol: variable i
location: class Example2
Example2.java:20: error: cannot find symbol
numbers[i]+"/"+numbers[i+1]);
^
symbol: variable i
location: class Example2
2 errors
-
Why are these compile-time error message generated?
The variable i
is now declared in the try
block so is not be recognized in the catch
block.
-
Move the declaration of
i
to the same statement that
declares ratio
(but leave the initialization in the
for
statement). What error message is generated by
compiling the application now?
Example.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.)
The try
block is treated as a single (block)
statement. From the compiler's perspective, this statement may
throw an exception causing the catch
block to be
entered before the statement is completed.
-
Initialize
i
before the
try
block (so it will compile).
What output is generated by this application now?
100/10=10
Couldn't calculate 10/0
-
Why aren't all of the divisions even attempted?
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 the catch
block. Control then leaves the catch
block and then
leaves
main
which causes the application to terminate.
-
Fix
Example2
so that it executes properly.
(Hint: Move the try-catch
block inside of the
for
block.) What is the body of the
main()
method now?
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]);
}
}
5. 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?
Example3.java
public class Example3
{
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");
}
}
}
-
Modify
Example3
so that it loops "properly" and
does not need to use a try-catch
statement. (Note: The output
should not change.) What is the body of the main()
method now?
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?
Probably not. The caller should know better than to pass
a
null
array into a method like this. So, specifying the
NullPointerException
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 a null
parameter but handles it with a
special return value.
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?
We can return null
here because double[]
is a reference type.
-
Is this an example of excessive
return
statements?
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 an else
clause and
that it reduces the amount of indenting in the "important part" of the
method.
6. 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?
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?
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"?
Example4.java
import java.util.*;
public class Example4
{
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");
}
}
}
-
What output will be generated by this application if
it is passed a single command-line argument containing the
String
"5.3+"?
-
Why? In particular, what exception is thrown and why?
A NoSuchElementException
is thrown by the statement
rightString = 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"?
One or more operands is not a number
-
Why? In particular, what exception is thrown and why?
A NumberFormatException
is thrown by the statement
rightOperand = Double.parseDouble(rightString);