| CS 261 | University of Puget Sound | Spring, 2020 |
|
Computer Science II
|
||
|
Abstract Data Types and their Implementations,
Some Basic Algorithms,
Object-oriented Problem Solving, and Efficiency |
||
Warning:
This course is under development.
Although the basic structure of this course is largely established,
nothing on this Web site should be considered official or even possibly
correct.
DO NOT MAKE PLANS BASED ON THE CONTENTS OF THIS SITE UNTIL JANUARY, 2020.
This laboratory provides background and practice with utilizing exceptions and exception handling within a Java program.
In implementing the Rational.java class, the resulting code handled many circumstances with constructors, arithmetic operations, and "toString" methods. However, coding in the previous lab did not address the possibility that the denominator of a fraction might be zero. In much computing, such matters typically are addressed in one of four ways:
The Ostrich Approach: Ignore the issue and hope it does not arise.
Identify a Special (or Sentinel) Return Value: For division
by 0, return NULL rather than a Rational object.
Set a [global] error variable: If an operation goes wrong, identify the problem within a variable that is stored globally. If processing proceeds without trouble, a "no-error" value is stored in the relevant variable.
Throw an Exception: Interrupt the normal flow of processing, and switch to a section of code designed for such matters.
In practice, each of these approaches is used to address issues in various operating systems and programming languages. For example,
the Unix operating system uses the Ostrich Approach when multiple processes are each waiting for resources allocated each other (in a condition called deadlock).
The block structured language SDL (Software Development Language) relied exclusively on special return values.
The C programming language sometimes uses return values and sometimes sets separate variables to identify potential errors.
The Java programming language utilizes exceptions.
Although each approach can work reasonably well in some contexts, each also has some obvious disadvantages.
The Ostrich Approach can lead to a program crashing or the need to reboot and rerun a program.
Both the sentinel and separate variable approaches may require that the result of a procedure needs to be checked after every call to determine if something went wrong. As a result, a simple procedure call may require substantial error-checking code that can obscure the logic of normally processing.
The exception approach requires a programmer to anticipate all possible issues of program execution at the time of development. In some cases, this may require extensive analysis—even for events that will occur rarely (or never) in practice.
With the use of exceptions, the idea is to break the normal flow of
processing, using an exception. In general, an exception is
any event that may require a change in the normal flow of
processing. In Java, a program "throws" an exception when the unusal
event is detected. Statements in the block calling the method
then"catch" the exception, allowing appropriate actionto be taken. To
illustrate this approach for the Rational example,
a divide method might be revised as follows:
Rational divide (Rational divisor) throws Exception {
int numerator = num * divisor.denom;
int denominator = denom * divisor.num;
if (denominator == 0){
throw new ArithmeticException ();
}
return new Rational (numerator, denominator);
}
With this refinement, normal processing is interrupted in
a main method or application if division within a
computation tries to divide with a denominator being zero. With
this approach normal processing can continue simply, but throwing
the exception can be reasonably clean. The structure of code within
main or an application can have the form
try {
.
Rational rat1 =
Rational rat2 =
.
result = div (rat1, rat2);
.
.
.
} catch
(ArithmeticException e) {
System.out.println ("attempted division by 0:");
System.out.println (" Exception caught: " + e);
}
In this try ... catch block, the try keyword
indicates that processing should shift to the catch section,
if any exceptions are encountered in the block. The block itself
can then focus on regular arithmetic computations.
More generally, a program can contain several try ... catch
blocks (even one for each arithmetical operation), and exceptions
can be handled differently in each one if desired.
In this lab, you will want to use your complete Rational
class that you developed earlier, based on the skeleton code in
Rational.java.
Following the discussion earlier in this lab, modify
your Rational from
the Working with Java and
Eclipse:
divide should throw
an ArithmeticException to avoid creating a fraction
with zero in the denominator.
main or a testing application should include one or
more try ... catch blocks to handle division by zero,
if it occurs.
Expand your testing from step 1 to include two or more sets of
arithmetic operations, each with its own try ... catch
block and each including division by zero. Each catch
block should print something different, so output from the program
will indicate which block(s) encountered a problem.
The initial documentation for the Rational class
specified, "Assertion: throughout, denom > 0"
Modify the constructor(s) as needed, so that
an InvalidObjectException is thrown, if the user tries
to create a Rational object with 0 in the denominator.
Adjust your main or testing application appropriately
to provide testing that includes the attempted construction of
a fraction with a denominator of zero.
Review the data fields and the getters and setters for
the Rational class. Which, if any, of these should
be private, eliminated completely, or modified? In each case,
justify your conclusion.
Discussion: Code development involving factions with zero in the denominator could be handled in several ways. Write a paragraph (at least 6 sentences) to address the following.
Rational with a zero denominator, the constructor
could create a valid object with a special value (e.g., 0/1 or
31416 / 28718). Presumably the special value could be
checked in an application to detect if an error occurred in the
constructor. Discuss the possible advantages and/or
disadvantages in throwing an exception versus constructing a
designated fraction. Ultimately, which approach seems better
and why?
InvalidObjectException in a
constructor and an ArithmeticException in
the divide method, is it possible to omit the
explicit throw ArithmeticException code
in divide and still avoid fractions with denominators
being zero?
throw ArithmeticException code
in divide might lead to an invalid fraction
(e.g., with a denominator 0), describe a test where this
might happen.
throw ArithmeticException would
still protect against zero in the denominator, explain how
this protection might work. Further, discuss the relative
merits of having exceptions thrown in several places
versus in just one place.
|
created 18 January 2020 revised 18 January 2020 |
|
| For more information, please contact Henry M. Walker at walker@cs.grinnell.edu. |