Week 5—Exceptions and I/O
written by Alexandros Evangelidis, adapted from J. Gardiner et al.
27 October 2015
This handout aims to cover:2 Exceptions
– Different types of exceptions
– When they should occur2 Input/Output
– Scanners
– Reading in from Files
– Streams and BufferedReaders
1 All About Exceptions
1.1 Errors Happen...
One of the problems with programming is users! You can never know when errors will occur: the best you
can do is guess what might cause them, and try to mitigate their effects. Sometimes, in fact, errors can occur
because of the programmer not anticipating what problems could arise. In order to cover all bases, Java provides
you with a convenient way to tell you when something has gone wrong: Exceptions and Errors. The word
Error actually has a special (bad) meaning in Java, and we won’t cover it further today. It suffices to say that
if your code produces an Error and not an Exception, something has gone wrong enough that Java cannot
recover.
Whenever something goes wrong in Java, it produces typically an exception. You will probably have seen a
few of these already: ArrayIndexOutOfBoundsExceptions, InputMismatchExceptions, NullPointer-
Exceptions to name some of the most common. They’re Java’s indication that something is wrong, but that
you could do something to fix it. Have a look at the code below: what error could occur with it?
1
Semester 1, Week 5 MSc/ICY Java Tutorial
import java .io . ∗ ;
import java .util . ∗ ;
public class InputTest{
public static void main(String [ ] args){
Scanner s = new Scanner(System .in ) ;
ArrayList intList = new ArrayList() ;
System .out .println("Enter some numbers!" ) ;
int currentVal = s .nextInt ( ) ;
while (currentVal != 99) {
System .out .println("Adding "+currentVal ) ;
intList .add(currentVal ) ;
currentVal = s .nextInt ( ) ;
}
System .out .println("Exiting: List is "+intList .toString ( ) ) ;
}
}
If you noticed that the possible exception is to do with the user’s input, well done. Consider what the line
int currentVal = s.nextInt() expects: the user should enter a number. If the user enters a String that
cannot be interpreted as a number such as ”b”, Java won’t be happy:
% java InputTest
Enter some numbers!
1
Adding 1
2
Adding 2
b
Exception in thread "main" java.util.InputMismatchException
at java.util.Scanner.throwFor(Scanner.java:840)
at java.util.Scanner.next(Scanner.java:1461)
at java.util.Scanner.nextInt(Scanner.java:2091)
at java.util.Scanner.nextInt(Scanner.java:2050)
at InputTest.main(InputTest.java:14)
Ouch! What does this mean? Well, it means you need to fix something.
What do you need to fix? That’s usually indicated by the first line of the exception. You can safely ignore
the in thread "main" bit, and look at the exception type, java.util.InputMismatchException.
This makes sense: nextInt expects an int, and we entered a String that cannot be interpreted as an int.
What’s then printed is called a stack trace: this is all of the methods that were called up to the point
where the exception occurred, beginning with the most recent. At the bottom, you can see that my class,
InputTest.java, caused the error at line 14, which is the second occurrence of s.nextInt(), in the loop.
2
Semester 1, Week 5 MSc/ICY Java Tutorial
Let’s have a look at another example, which we’ll put after that code:
s = new Scanner(System .in ) ;
String [ ] stringArray = new String [ 5 ] ; // only f i v e e lements . . .
System .out .println("Enter five strings" ) ;
for (int i = 1 ; i <= 5 ; i++) {
String line = s .nextLine ( ) ;
stringArray [i ] = line ;
System .out .println("Added "+line+" at index "+i ) ;
}
System .out .println("Done: "+Arrays .toString(stringArray ) ) ;
What error will occur here? This time, it’s to do with the length of the array:
Enter five strings
this
Added this at index 1
is
Added is at index 2
a
Added a at index 3
string
Added string at index 4
and
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 5
at InputTest.main(InputTest.java:22)
The error is much easier to understand here: Again, we’re told we have an exception in the main thread (which
we can ignore). Then we can see that it’s an ArrayIndexOutOfBoundsException, and the number 5, which
relates to the index of the array at which the exception occurred. This makes sense, because the array is size 5
(meaning the index of the last element is 4), and we’re trying to access index 5. As expected, line 22 points to
the line stringArray[i] = line.
1.2 A Little More about Exceptions
There are hundreds, if not thousands of types of Exception. When you see an exception on your screen, it’s
because it’s been thrown by Java. As such, all Exceptions extend (directly or otherwise) the class Throwable:
Look at ArrayIndexOutOfBoundsException in the API:
java.lang.Object
extended by java.lang.Throwable
extended by java.lang.Exception
extended by java.lang.RuntimeException
extended by java.lang.IndexOutOfBoundsException
extended by java.lang.ArrayIndexOutOfBoundsException
Later, we’ll consider writing our own exceptions, which will also extend the Exception (or RuntimeException
class).
That actually raises an interesting point—there are two sorts of Exception: those that can occur anywhere,
and as such can’t really be planned for by the compiler, and those that Java knows can occur, and as such forces
you to do something about. Exceptions like ArrayIndexOutOfBoundsException can occur anywhere—
since they happen during the execution of a program, without predictability, they are called runtime exceptions,
and extend RuntimeException somewhere in their hierarchy.
3
Semester 1, Week 5 MSc/ICY Java Tutorial
Other exceptions just extend the Exception class, without RuntimeException, somewhere in their
hierarchy. In this case, Java knows that it’s possible the exception could occur, and forces you to do something
about it. We’ll talk more about this type of exception later.
1.3 What do you mean, “do something about it”?
Well, we’ve said that exceptions can be thrown. To handle them, they need to be caught. To do this, we wrap
the code that could cause an exception in something called a try...catch block:
try {
// code that could cause an except ion
} catch( SomeExceptionType et ) {
//what to do when that except ion occurs
}
Our main aim in catching exceptions is to prevent the program quitting with an error. So, we surround the
‘dangerous’ code with a ’try block’ (the try{ ... } code). If no exceptions occur, we continue with
execution of the code below the block. If an exception does occur, we consider the catch block(s). If the
exception thrown matches the type in the round brackets, we give it a name, et in the example above, and
then do something with it. We could have multiple catch blocks: if it’s not the first exception, try the next
down, and so on:
try {
// code that could cause an except ion
} catch( SomeExceptionType et ) {
// what to do when that except ion occurs
} catch( AnotherExceptionType et2 ) {
// do something e l s e
} catch( Exception e ) {
// t h i s w i l l catch a l l other except i ons
}
You should think of these multiple catch blocks as being progressively bigger nets, each catching a wider range
of exceptions, with the first one being the most specific.
1.3.1 So what goes in the catch block?
Let’s look at an example like the one above, and then think about it:
Scanner s = new Scanner(System .in ) ;
ArrayList intList = new ArrayList() ;
System .out .println("Enter some numbers!" ) ;
int currentVal ;
while(true) {
try{
currentVal = s .nextInt ( ) ;
if ( currentVal == 99 ) {
break ;
}
System .out .println("Adding "+currentVal ) ;
intList .add(currentVal ) ;
4
Semester 1, Week 5 MSc/ICY Java Tutorial
} catch(InputMismatchException ime) {
System .out .println(ime+": please enter only numbers." ) ;
s = new Scanner(System .in ) ;
}
}
Now, every time we enter something that isn’t a number, what happens? Java throws an exception, and it’s
caught by the catch block. We print out the type of the exception, reset the Scanner to how it was initalised,
and then finish the code block (which takes us back to the top of the while loop). All without crashing!
Sometimes, you’ll want to do something else when you discover an error. To use an example from the lab lec-
ture, if the number for a specific grayscale value exceeds 255, Java will raise an IllegalArgumentException
(very common where parameters should be within certain bounds). You could use the catch block in this case
to reset the value to 255, to avoid halting the program halfway through. Often, you might just print out that
an error has occurred, and allow the user to continue below the try block.
2 I/O
2.1 Scanners
By now, you all know how to make Java print, using the System.out.println command. Quite often though, you
need also to be able to read in:
Scanner sc=new Scanner(System .in ) ;
String aLineOfText=sc .readLine ( ) ;
Scanner is a useful tool that allows you to grab input from the keyboard. In order to use it, you need
to add import java.util.Scanner to the top of your classes, to tell Java to include it in your code. In
this example, when I write sc.readLine(), I’m reading a line of text from the keyboard, and assigning
it to aLineOfText. Scanner provides a number of other useful input types, such as sc.nextInt() or
sc.nextDouble():
System .out .println("Pick an integer:" ) ;
int value=sc .nextInt ( ) ;
System .out .println( "You picked " + value +
". The square of that is " + (value∗value) + "." ) ;
If you enter something that isn’t an int, Java will throw an error and crash. In these cases an exception
will be thrown.
2.2 More Powerful Input
Much input in java is done with streams, which are read by stream readers. An example is keyboard input: this
can occur via the InputStream object System.in:
InputStreamReader isr = new InputStreamReader(System .in ) ;
char character = (char) isr .read ( ) ; // read a s i n g l e cha rac t e r
As you might imagine, reading a single character at a time is a little tiresome. What is needed is some way to
buffer characters into a line at a time, and for this we can use a BufferedReader, which wraps around an
InputStreamReader:
import java .io . ∗ ;
public class MyReader{
public MyReader ( ){
BufferedReader br = new BufferedReader(new InputStreamReader(System .in ) ) ;
5
Semester 1, Week 5 MSc/ICY Java Tutorial
Now, I can be quite specific about how I want to read from the keyboard. Let’s say that I want to read lines
from the keyboard into an ArrayList of Strings until the user enters “stop”:
ArrayList stringList = new ArrayList() ;
String currentLine="" ;
while( ! (currentLine=br .readLine ( ) ) . equals("stop") ) {
stringList .add(currentLine ) ;
}
// p r in t the l i s t out
}
Of course, we could simplify the code about to make it more comprehensible:
String currentLine=br .readLine ( ) ;
while ( !currentLine .equals("stop" ) ) {
stringList .add(currentLine ) ;
currentLine = br .readLine ( ) ;
}
One particularly useful thing about using BufferedReaders with InputStreamReaders, though, is that they
can be applied to anything which provides an InputStream to read from: you saw in the lectures that you
could read from a webpage by specifying a URL:
BufferedReader webReader =
new BufferedReader(
new InputStreamReader(
new URL("http://www.myURL.com" ) .openStream ( ) ) ) ;
We could also use BufferedReader to read directly from a file. We do this by using the class FileReader,
which extends InputStreamReader directly, and takes a File, or a filename, as an argument:
BufferedReader fr =
new BufferedReader(
new FileReader(
new File("/path/to/my/file.txt" ) ) ) ;
// equ iva l en t to
BufferedReader fr2 =
new BufferedReader(
new FileReader("/path/to/my/file.txt" ) ) ;
You can really use whichever sort of reader you want (Scanner or BufferedReader) for the current exercise. I
would recommend that you get to grips with using BufferedReaders, though, as they’re quite useful. They
do have one problem, though. . .
6
Semester 1, Week 5 MSc/ICY Java Tutorial
3 Exceptions: Part Deux
Pretty much everything in the package java.io has the potential to generate exceptions, which you need to
handle. You should be able to see the obvious potential for exceptions when trying to read from a file: what if
the file isn’t there? Java obviously can’t read from it, and so has to raise an exception.
However, the sort of exceptions we’re dealing with here aren’t the RuntimeExceptions from above: they’re
known as checked exceptions.
3.1 Checked vs. Unchecked
We’ve mentioned a number of exceptions already. I’m sure you remember them:
String [ ] arr = new String [ 6 ] ;
System .out .println(arr [ 6 ] ) ; //what happens here ?
System .out .println(arr [ 3 ] . length ) ; //what about here ?
Object x = new Integer ( 0 ) ;
System .out .println ( (String)x ) ; //how about now?
All of the above examples are so-called unchecked exceptions. There are a couple of reasons for this name.
Unchecked exceptions all extend the class RuntimeException, and the compiler will not force you to catch
them. This is because it cannot usually tell whether these exceptions are likely to occur during runtime, and
that is because unchecked exceptions are generally down to programmer error: i.e., the programmer is probably
doing something stupid. That much should be clear from the above examples.
Generally, if you get an unchecked exception from your code, it’s your fault, and you should do something
to try to fix it. Simply catching these exceptions may not be getting to the root of the problem.
Checked exceptions are slightly different. Java knows that these are likely to occur, and forces you to catch
them. The java.io package is a good example. If I try to compile the following code:
import java .io . ∗ ;
public class Test{
public static void main(String [ ] args) {
BufferedReader br = new BufferedReader(new FileReader("file.txt" ) ) ;
}
}
compilation wouldn’t work. The compiler will tell me:
Test.java:4: unreported exception java.io.FileNotFoundException;
must be caught or declared to be thrown
The semantics of this sentence need to be understood: either the exception FileNotFoundException must
be caught by my code, or my code must say that it deliberately doesn’t handle the exception, but throws it to
the method from where it was called. In the code above, it would be a very bad idea to allow main to declare
that it throws FileNotFoundException. Instead, I should handle the possibility that the file isn’t there:
public static void main(String [ ] args){
try {
BufferedReader br = new BufferedReader(new FileReader("file.txt" ) ) ;
} catch (FileNotFoundException fnfe) {
System .out .println("File not found: " + fnfe .getMessage ( ) ) ;
} catch (IOException ioe) {
System .out .println("Miraculously, some other IO exception:" + ioe .getMessage ( ) ) ;
}
// read from f i l e here
}
7
Semester 1, Week 5 MSc/ICY Java Tutorial
As you might have guessed, the above code is as such not useful (why not?), though it would now compile.
3.2 Causing exceptions to happen
Sometimes, you’ll want to force your own exceptions to happen, if a user does something which you want to
forbid. For this reason, you can cause an exception with the throw keyword:
public void getSomeNumbers(int first , int second){
if ( first+second >= 20 ) {
throw new IllegalArgumentException("The numbers you’ve entered are not acceptable." ) ;
}
System .out .println("Your numbers are fine. Well done." ) ;
}
A couple of important things to finally note: if you throw an exception, it must be caught somewhere (or the
program crashes because of it). Eg:
public void myExceptionHandlingMethod ( ) {
try {
dangerousMethod ( ) ;
} catch(IdiotUserException iue) { /∗ handle e r r o r ∗/ }
}
public void dangerousMethod(User u) throws IdiotUserException
{
if (u .isIdiot ( ) ) {
throw new IdiotUserException ( ) ;
}
}
Lastly, it’s considered good practice to only throw exceptions when you want to show that error behaviour
has occurred. Exceptions shouldn’t be used for other reasons.
3.3 Using Finally
When dealing with files (or I/O generally), there’s an extra part to the try...catch block, called the finally
block. What we put in this block is things that must be done, irrespective of whether an exception was thrown
or not. The most common that you’ll need to worry about is when reading or writing using streams: those
streams must be closed once you’re done with them. In the example below, like the one from the lectures, we’re
copying a file:
8
Semester 1, Week 5 MSc/ICY Java Tutorial
FileInputStream source = null ;
FileOutputStream target = null ;
try {
source = new FileInputStream ("/path/to/my/file.txt" ) ;
target = new FileOutputStream("/path/to/copy.txt" ) ;
for (int c = source .read ( ) ; c != −1; c = source .read ( ) ) {
target .write(c ) ;
}
} catch (IOException e) {
System .err .println("IO error " + args [ 0 ] ) ;
System .err .println(e .getMessage ( ) ) ;
exitCode = abnormalTermination ; // We’ ve changed our mind .
} finally {
try{
source .close ( ) ;
target .close ( ) ;
} catch(IOException e) { /∗ handle e r r o r ∗/ }
}
If you were just reading using a BufferedReader, you should call close on that. Note that as the close
method itself can throw an IOException, it too should be enclosed in a try...catch (this could get
complicated)!
9