Exceptions-IO - Carnegie Mellon University

Download Report

Transcript Exceptions-IO - Carnegie Mellon University

Object-Oriented Programming
95-712
MISM/MSIT
Carnegie Mellon University
Lecture 9: Exception Handling, I/O
Today’s Topics



Older, and more modern, strategies for error
handling.
Exception handling basics.
Some exception details:
– plain Exceptions vs. RuntimeExceptions
– Exceptions containing information
– Exception hierarchies


What’s really practical using exceptions.
An introduction to the Java I/O library.
Exception Handling

The compiler is supposed to report syntax errors,
it but can’t discover many other types of errors:
–
–
–
–
–
–
–

casts to the wrong type
files that don’t exist
dividing by zero
indexing out of bounds
incorrect data formats
badly formed SQL queries
etc. etc.
Unresolved, many of these errors prevent a
program from continuing correctly.
Exception Handling

We would like to be aware of these
“exceptional” situations, in hopes of
–
–
–
–
–

recovery
retrying
trying an alternate strategy
cleanup before exit
or, just finding out where the problem is!
Nothing is more mystifying than a program
that just “goes up in smoke”!
Exception Handling
Whadda I do
now???
Exception Handling
What shall we
do now???
Exception Handling
Ahh, I know
what to do!
Strategies for Error Handling





In the course of programming, we constantly test
for situations that routinely arise.
We include logic to deal with the possibilities
(switch, if-else, etc.).
“Exceptional” situations are different. They are
things that “should never happen”.
We expect our code will be free from bugs, but…
We’re usually wrong.
Strategies for Error Handling
Pre-testing the arguments to each function
call.
 Checking return values indicating error.
 Setting and checking global error variables.
 These are not formal methods, not part of
the programming language itself.
 They require programmer discipline (but so
does the use of exceptions…).

Example
void workOnArray(double[] myArray, int otherInfo) {
int i = 0;
// complicated calculation of array index i, using otherInfo
myArray[i] = 3.14159;
// what if i is out of bounds?
}
Example (cont.)
int workOnArray(double[] myArray, int otherInfo) {
int i = 0;
// complicated calculation of array index i, using otherInfo
if (i >= 0 && i < myArray.length) {
myArray[i] = 3.14159;
return 0; // indicating everything OK
}
else
return -1; // indicating an error
}
Potential Problem
What if workOnArray() needs to return a
value (say, a double)?
 The “C” approach: values are returned
through additional reference arguments in
the method:

int workOnArray(double[] myArray, int otherInfo,
double returnValue)

This quickly gets cumbersome.
Another Technique: Globals
There are no true global variables in Java,
but we “fake it” all the time.
 Write a class with static variables!
 These are effectively available anywhere in
a program, and could be used to signal error
conditions.

Faking A Global Variable
public class MyGlobal {
static int indexError;
MyGlobals() { } // indexError automatically initialized to 0
}
void workOnArray(double[] myArray, int otherInfo) {
int i = 0;
// complicated calculation of array index i, using otherInfo
if (i >= 0 && i < myArray.length) {
myArray[i] = 3.14159;
}
else
MyGlobal.indexError = -1;
}
Three Important Issues

Where should the tests be done?
– Before the array is “indexed into”?
– By the array class itself?

Where should the error be reported?
– Locally, or “further down” in the call stack?
– Stroustrup says that authors of libraries can’t know
their user’s contexts, so can’t know what to do.


Who is responsible for adjudicating the error?
Exception handling in Java helps with these
problems, but doesn’t completely solve them.
GPTree Exception Example

Child Nodes are added to a Binop like this:
public void setChild(int position, Node n) {
if (position == 1)
lChild = n;
else // not 1, so must be 2, right?
rChild = n;
}

position values for Binop should only be 1 or 2,
it’s an error otherwise. But we might decide later
to add Ternop, so the abstract Node method
should allow for values other than 1 or 2.
Solution: Add an Exception Class
public class GPTreeChildPositionException extends Exception { }
public abstract class Node{
public abstract void setChild(int position, Node n)
throws GPTreeChildPositionException ;
:
}
public abstract class Binop extends Node {
public void setChild(int position, Node n)
throws GPTreeChildPositionException {
if (position == 1)
lChild = n;
else if (position == 2)
rChild = n;
else
throw new GPTreeChildPositionException();
}
:
}
Exceptions at Work
public static void main(String[] args) {
double[] data = {3.14, 2.78, 1.0};
Node root = randOperator();
Node n1 = randOperator();
try { // any place setChild() is called must be in a try block!
n1.setChild(1, randConstOrVariable());
n1.setChild(3, randConstOrVariable()); // Unghh! an error!
Node n2 = randOperator();
n2.setChild(1, randConstOrVariable());
n2.setChild(2, randConstOrVariable());
root.setChild(1, n1);
root.setChild(2, n2);
}
catch(GPTreeChildPositionException e) {
System.out.println("Caught a GPTreeChildPositionException");
}
}
Points to Note

We included in setChild() a statement throwing a
GPTreeChildPositionException.

So we must let the users of the method know this,
by stating
setChild(int position, Node n)
throws GPTreeChildPositionException


We have to do this in the abstract superclass as
well.
If we don’t the compiler gives an “unreported
exception” error.
More Points
The compiler insists any call to this method
be “tested” by enclosing it in a try block, or
else we get an “unreported exception” error.
 This is the same error message, but for a
slightly different reason (confusing?).
 If we do include a try block (which we
must!), there has to be a corresponding
catch block or finally clause.

Even More Points
All of this is true because our exception
extended the Exception class.
 If we extend RuntimeException instead,
we don’t need to say throws, nor include
try and catch blocks.
 RuntimeExceptions are special; the Java
runtime system takes care of them
automatically.
 This can be a real advantage.

Exception vs. RuntimeException

Here is the output when deriving from Exception:
Caught a GPTreeChildPositionException

Here is the result when implementing
RuntimeException with no try-catch blocks:
GPTreeChildPositionException
at Binop.setChild(Binop.java:17)
at TestAlgebra.main(TestAlgebra.java:47)
Exception in thread "main"
Exceptions Always Get Caught
public class NeverCaught {
static void f() {
throw new RuntimeException(“From f()”);
}
static void g() {
f();
}
public static void main(String[] args) {
g();
}
}
“Uncaught” Exceptions

If an exception makes it all the way “back” to
main() without being caught, the Java runtime
system calls printStackTrace() and exits the
program:
java.lang.RuntimeException: From f()
at NeverCaught.f(NeverCaught.java:5)
at NeverCaught.g(NeverCaught.java:8)
at NeverCaught.main(NeverCaught.java:11)
Exception in thread "main"

You can call printStackTrace() yourself if you
want (and it’s useful to do it).
Call Stack, Normal Execution
main() called
f() called
g() called
f()
main()
main()
time
g()
f()
main()
g() finished
f()
main()
f() finished
main()
Call Stack, With Exception
main() called
f() called
g() called
main()
f()
main()
search f() for
handler, exit f()
g()
f()
main()
exception thrown,
g() has no handler,
exit g()
f()
main()
main()
search main() for
handler, call
printStackTrace(),
exit main()
Exceptions Containing Information

Exceptions are real objects (created with
new), and so can have constructors and data
members.
public class GPTreeChildPositionException extends Exception {
int i;
GPTreeChildPositionException(){}
GPTreeChildPositionException(String msg) {
super(msg);
}
GPTreeChildPositionException(int i) {
this.i = i;
}
}
Using the Information
try {
n1.setChild(1, randConstOrVariable());
n1.setChild(3, randConstOrVariable()); // error
Node n2 = randOperator();
n2.setChild(1, randConstOrVariable());
n2.setChild(2, randConstOrVariable());
root.setChild(1, n1);
root.setChild(2, n2);
}
catch(GPTreeChildPositionException e) {
System.out.println(“Caught a ” +
“GPTreeChildPositionException, position = ” + e.i);
}
Catching Any Exception
We are always interested in exceptions that
implement the interface Exception.
 So, a catch block like

catch(Exception e) {
System.out.println(“Caught an exception”);
}
will catch any exception.

If you have multiple catch blocks, this one
should be last.
Multiple Catch Blocks

There may be several possible errors generated by a
block of code:
try {
// try this
// and this
}
catch(GPTreeChildPositionException e) {
System.out.println(“Caught child position exception”);
}
catch(Exception e) {
System.out.println(“Caught some other exception”);
}
Rethrowing an Exception


Suppose you’ve caught an exception, and decided
you can’t recover from it, but perhaps a higher
context can.
You can rethrow it:
catch(Exception e) {
System.out.println(“An exception was caught”);
throw e;
}

The stack trace remains unchanged if it is caught
higher up.
Catching, Fixing and Retrying
public class Retry {
static int i = 0;
public void f() {
try { g(); }
catch(Exception e) {
System.out.println("Caught exception, i = " + i);
i++;
f();
}
}
void g() throws gException {
if (i < 3) { throw new gException(); }
else
System.out.println("g() is working now");
}
This Can Be Dangerous
public class Retry {
int i = 0;
boolean fileIsOpen = false;
public void f() {
try {
if (fileIsOpen)
System.out.println("Opening an already opened file!");
else
fileIsOpen = true; // i.e., open the file
g();
fileIsOpen = false; // i.e., close the file
}
// file will be left open: Dangerous!
What’s So Dangerous?

Just close the file in the catch block? Good
idea! But, what if some other exception
were thrown, one that you didn’t catch?
catch(gException e) {
System.out.println("Caught exception, i = " + i);
i++;
fileIsOpen = false;
f();
}
finally {
fileIsOpen = false;
}
Exception Hierarchies
Exceptions are classes, so can be in
inheritance hierarchies.
 The usual polymorphism rules apply.
 A handler for a superclass exception will
catch a subclass exception.
 This makes it easy to catch groups of
exceptions.

The GPTree Hierarchy
public class GPTreeException extends Exception {
GPTreeException() { }
GPTreeException(String msg) {
super(msg);
}
}
public class GPTreeChildPositionException
extends GPTreeException {
int i;
// constructors
}
public class GPTreeRandOpException
extends GPTreeException {
int i;
// constructors
Declare the New Exception
public static Node randOperator() throws GPTreeRandOpException {
Node n;
int i = r.nextInt(5);
// this is an error!
switch(i) {
case 0: n = new Plus(); break;
case 1: n = new Minus(); break;
case 2: n = new Mult(); break;
case 3: n = new Divide(); break;
default: throw new GPTreeRandOpException(i);
}
return n;
}
Then Catch It As GPTreeException
try {
Node root = randOperator();
// add all the children
String s = root.toString();
System.out.println(s + " = " + root.eval(data));
}
catch(GPTreeChildPositionException e) {
System.out.println(“Caught a ”
+ “GPTreeChildPositionException, position = ” + e.i);
}
catch(GPTreeException e) {
System.out.println("Caught a GPTreeException");
}
Termination Vs. Resumption

Java makes it hard to complete this cycle:
–
–
–
–
find a problem,
throw an exception,
fix the problem in the handler, and
go back to where you left off.
This is called “resumption”.
 Java assumes you don’t want to go back.
 This is called “termination”.

What You Can Do






Fix the problem and call the method that caused
the exception once more.
“Patch things up” and continue without retrying
the method.
Calculate some alternative result.
Do what you can in the current context, and
rethrow the same exception to a higher context.
Do what you can, and throw a different exception
to a higher context.
Terminate the program (how?).
Java Input/Output
I/O libraries are hard to design, and
everyone can find something to complain
about.
 Java’s I/O library is extensive, and it seems
like you need to know 100 things before
you can start using it.
 Today, let’s learn just five things and still do
something useful.

#1: Why Is Java I/O Hard?

Java is intended to be used on many very different
machines, having
– different character encodings (ASCII, EBCDIC, 7- 8or 16-bit…)
– different internal numerical representations
– different file systems, so different filename & pathname
conventions
– different arrangements for EOL, EOF, etc.

The Java I/O classes have to “stand between” your
code and all these different machines and
conventions.
#2: Java’s Internal Characters








Unicode. 16-bit. Good idea.
So, the primitive type char is 16-bit.
Reading from a file using 8-bit ASCII characters
(for example) requires conversion.
Same for writing.
But binary files (e.g., graphics) are “byte-sized”,
so there is a primitive type byte.
So Java has two systems to handle the two
different requirements.
Both are in java.io, so import this always!
I don’t show imports in the examples below.
Streams
InputStream
Class
Host Machine
ASCII?
EBSDIC?
byte
These classes are
tailored to a specific
host machine.
Java Program
Using Bytes
byte
OutputStream
Class
Readers and Writers
Reader Class
InputStream
Class
char
Host Machine
ASCII?
EBSDIC?
Java Program
Using Unicode
Writer Class
char
OutputStream
Class
#3: Is Java “Platform
Independent”?
Yes, to the extent that you, the Java
programmer, needn’t care about the
platform your code will run on.
 No, to the extent that the Java I/O classes,
the compiler, and any browser your clients
use, must be programmed specifically for
the host machine.
 This is not a new idea, just well-hyped by
Sun (recall “p-code” from the 1970’s).

#4: What Are The Input Sources?



System.in, which is an InputStream connected to
your keyboard. (System is public, static and
final, so it’s always there).
A file on your local machine. This is accessed
through a Reader and/or an InputStream, usually
using the File class.
Resources on another machine through a Socket,
which can be connected to an InputStream, and
through it, a Reader.
#5: Why Can’t We Read Directly
From These?
We can, but Java provides only “low-level”
methods for these types. For example,
InputStream.read() just reads a byte…
 It is assumed that in actual use, we will
“wrap” a basic input source within another
class that provides more capability.
 This “wrapper” class provides the methods
that we actually use.

“Wrapping”

Input comes in through a stream (bytes), but
usually we want to read characters, so “wrap”
the stream in a Reader to get characters.
public static void main(String[] args) {
InputStreamReader isr = new InputStreamReader(System.in);
int c;
try {
while ((c = isr.read()) != -1)
System.out.println((char) c);
}
catch(IOException e) {
}
InputStreamReader
This is a bridge between bytes and chars.
 The read() method returns an int, which
must be cast to a char.
 read() returns -1 if the end of the stream has
been reached. Thus the program above will
never stop, since we can’t type the “end-ofstream” value.
 We need more methods to do a better job!

Use a BufferedReader
public static void main(String[] args) {
BufferedReader br =
new BufferedReader(new InputStreamReader(System.in));
String s;
try {
while ((s = br.readLine()).length() != 0)
System.out.println(s);
}
catch(IOException e) {
}
}
“Transparent Enclosure”
adds readLine()
and buffering
“looks like” a Stream
“looks like” a Reader
BufferedReader
InputStreamReader
adds read()
“looks like” a Stream
“looks like” a Reader
System.in
(InputStream)
an abstract class
Reading From a File
The same idea works, except we need to use
a FileInputStream.
 Its constructor takes a string containing the
file pathname.

public static void main(String[] args) throws IOException {
InputStreamReader isr = new
InputStreamReader(new FileInputStream("FileInput.java"));
int c;
while ((c = isr.read()) != -1)
System.out.println((char) c);
isr.close();
}
Reading From a File (cont.)




Here we check for a -1, indicating we’ve reached
the end of the file.
In Together, this works just fine, but an absolute
path name is safer.
The read() method can throw an IOException,
and the FileInputStream constructor can throw a
FileNotFoundException
Instead of using a try-catch construction, this
example shows main() declaring that it throws
IOException. This is a “dirty trick”.