Skip to content

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.

  1. 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;
    }
    
  2. 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 be null.
  3. 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 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.

  4. Modify the declaration of the toPercentages() method so that it specifies the NullPointerException. What is the declaration now?

    Answer
    public static double[] toPercentages(double[] values) throws NullPointerException
    
  5. 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?

    Answer
    if (values[i] < 0.0)
    {
        throw new IllegalArgumentException("Element " + i + "is < 0");
    }
    
    total += values[i];
    
  6. Why don't you need to include an else clause in the if statement?

    Answer

    Because the throw statement returns control to the caller (in essentially the same way as a return statement, though to a different place).

  7. 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 one IllegalArgumentException will be thrown, when the first negative element is encountered.

  8. Modify the declaration of the toPercentages() method so that it now also includes IllegalArgumentException. What is the declaration now?

    Answer
    public static double[] toPercentages(double[] values)
        throws IllegalArgumentException, NullPointerException
    
  9. 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?

    Answer
    if (total <= 0.0)
    {
        throw new IllegalArgumentException("Total is 0");
    }
    
  10. 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.

  1. 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.
    
  2. 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)
    
  3. 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 an ArithmeticException. Since the exception is not caught, the application terminates and an error message is printed to the console.

  4. 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 statements 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 statement that generated the exception if you didn't change the white space.) What statements are now in the body of the main() 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
    
  5. What error message is generated by compiling this version of the application?

    Answer

    Something like:

    Part2.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.

  6. Why is this compile-time error message generated?

    Answer

    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.

  7. 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.

  8. What output is generated by this application?

    Answer
    Divide by 0.
    Done.
    
  9. 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.

  10. 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?

    Answer
    catch (ArithmeticException ae)
    {
        System.out.println("Divide by 0.");
        ae.printStackTrace();
    }
    
  11. 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.
    

  12. 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.)

  13. 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?

    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.

  1. 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
    
  2. Why are these compile-time error message generated? (Hint: Think about block statements and block scope.)

    Answer

    The variable i is declared in the for statement and, so, is not recognized outside of it.

  3. 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?

    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
    
  4. Why are these compile-time error message generated?

    Answer

    The variable i is now declared in the try block so is not be recognized in the catch block.

  5. 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?

    Answer
    Part3.java:20: variable i might not have been initialized
                                numbers[i]+"/"+numbers[i+1]);
                                        ^
    
  6. 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 the catch block to be entered before the statement is completed.

  7. Initialize i before the try block (so it will compile). What output is generated by this application now?

    Answer
    100/10=10
    Couldn't calculate 10/0
    
  8. 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 the catch block. Control then leaves the catch block and then leaves main which causes the application to terminate.

  9. Fix Part3 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?

    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.

  1. 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
    
  2. Modify Part4 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?

    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");
    
  3. 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 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.

  4. Modify the toPercentages() method so that it checks for a null 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;
    }
    
  5. 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 because double[] is a reference type.

  6. 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 an else 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.

  1. What functionality does a StringTokenizer object provide?

    Answer

    It can be used to break a String into component parts, called tokens.

  2. 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.

  3. 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
    
  4. 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
    
  5. Why? In particular, what exception is thrown and why?

    Answer

    A NoSuchElementException is thrown by the statement rightString = tokenizer.nextToken();.

  6. 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
    
  7. Why? In particular, what exception is thrown and why?

    Answer

    A NumberFormatException is thrown by the statement rightOperand = Double.parseDouble(rightString);.

Submission

Answer the questions in Canvas for your section's Lab 6 assignment based on this lab.