Today, you'll be working with assertions and with exceptions, with particular attention to defining and throwing exceptions.
Clone the repository for today's lab:
hg clone /cslab/class/csci235/labs/lab13
We have frequently documented pre- and post-conditions for methods,
and we have noted that if the caller does not satisfy the
preconditions of a method, the behavior of the method is not defined.
When a program does not work correctly, it would often be helpful to
determine whether the fault lies in a class/method or in its caller.
To assist in that, Java provides the assert
statement,
which consists of the reserved word assert
followed by a
boolean expression.
Normally, an assertion is treated like a comment, except for the
fact that the expression must be able to be compiled. When you read a
program with assertions, you know that a particular condition must be
true at that point in the code. In addition, you can turn on checking of
assertions when you run a program by giving the
-enableassertions
option on the Java command line. Java
will then evaluate the boolean expression every time an assert
statement is reached, and it will throw an AssertionError
if the expression is not true.
(Assertions are described briefly on pages 153-154 of the textbook.)
It can be especially helpful to write assertions for each method's preconditions. It is sometimes helpful to also assert postconditions, and, when the code is tricky, it may be helpful to assert things like loop invariants. In the first part of this lab, you will practice writing preconditions and assertions for them.
Change into the assert
subdirectory for this lab.
What you'll find there is a collection of classes that implement a
slightly smarter array, supporting insertion, removal, and subarray,
with a driver program for testing them.
What is missing in this case is the documentation of the
preconditions for each of the methods in SmartArray
.
For each method in SmartArray
, you need to
assert
statements that check these
preconditions.
To see your assertions in action, you need to run the
java
command with the flag
-enableassertions
, as in
java -enableassertions ArrayOps
You can abbreviate -enableassertions
to
-ea
.
Change into the excepts
directory. You will find there
almost the same program. The key difference is that the methods of the
SmartArray
class in this version do not have
preconditions; they are instead supposed to throw a
SmartArrayException
if they are called with bad arguments.
Take a look at SmartArrayException.java
, which
provides a minimal SmartArrayException
class. There are
several things to note about this definition:
Exception
on the end of
classes that are exceptions.
extends Exception
. This similar to the
way that a class can implement
an interface, but the
supertype here is a class instead of an interface. The class that
we extend will usually be one of Exception
,
RuntimeException
, or Error
.
String
that contains a
message about the exception.
super()
does.
Whenever you write an exception class, it will need to do these same things. You might provide other constructors if it makes sense to do so. If you want to get fancier (beyond the scope of this semester), you could replace other parts of the framework by writing additional methods.
In the SmartArray.java
in this directory, the
PRECONDITION
lines are gone, but the methods that
previously had preconditions have a throws
clause to
indicate that might throw a SmartArrayException
.
What you need to do now is to make each of those throw an appropriate
exception if it is called with bad parameter values. You probably
want to use the constructor that takes a string; make the message you
pass something that will be helpful.
Once you see that it is working, you should commit all of your changes to your Mercurial repository.
If you have time, modify code in ArrayOps
to catch and
handle the exceptions better. In particular, print a useful message
and let the program continue.
Commit to your repository before proceeding.
You can use the extends
keyword to make additional
classes that are subtypes of SmartArrayException
. Doing
that would allow the caller to distinguish among them by using
multiple catch
clauses.
You've been provided with
AnotherSmartArrayException.java
as an example of what
this might look like. You could define your own classes that
similarly extend SmartArrayException
on a similar
pattern. For example, you might distinguish between the cases of
an index out-of-bounds (calling it, say,
SmartArrayIndexException
) and a bogus subrange (a
SmartArraySubrangeException
, when end is before start). The
methods in SmartArray
could then throw any of these
exception classes.
The caller could catch the more specific case (or cases) by
including an additional catch
clause for the subtype.
Note that because of the way Java matches exceptions to throw clauses,
you should always order exceptions from more specific to more general
(from subtypes to supertypes). A clause for
AnotherSmartArrayException
has been included in
ArrayOps
, even though no methods (currently) throw it.
So now you should be able to
SmartArray
to
throw the more specific type; and
ArrayOps
to catch the more specific type as
well as SmartArrayException
.
And, of course, commit to Mercurial.
To script your the execution of your programs
asserts
subdirectory,
compile both source files, and the run the program several times
with assertions enabled to show that it works for good input
and fails appropriately for bad input.
except
directory
(cd ../excepts
), compile all of the Java files
(javac *.java
), and again run your program several times
to trip over a variety of the exceptions you throw.
After you end the script, make sure that you are in the top directory, and then
/cslab/class/csci235/bin/handin lab13 .(note the dot!) to hand it all in.