Exercise 5: Handling Exceptions
Assignment: Create a new application
that will write Hello's greeting to a file.
Here's how it can be done. Looking at the Hello.java
source code, we see that it provides us with
sayHello(Writer), a version that will write the greeting
to an arbitrary output stream. (You might have a look at the
Hello.java source code to see how it does it.) We just
need to provide it with that stream. Thus, we don't really need to
create a sub-class of Hello in order take advantage of
this method. Instead, our new class will just be a user of
the Hello class. And, in fact, we can do everything we
need in just a main() method.
So we'll start with a skeleton, which we put into a file named after
our class, HelloToFile.java:
package nvoss.basicjava;
/**
* a Hello app that writes its greeting to a File
*/
public class HelloToFile {
/**
* say hello to the world. The user must provide the name of a
* File to write to.
*/
public static void main(String[] args) {
}
}
Notice:
- We're putting this in the same package,
nvoss.basicjavaas our other classes. - We don't need to extend anything.
- We've added a
main()method. As the comment indicates, we're going to let the user give us the name of the file to write to.
Now we can fill out the main()implementation. First we
need our Hello object, so we create one:
public static void main(String[] args) {
Hello greeter = new Hello();
}
Next, we need open up the output file. To figure out how to do this, let's consult the Java Package API documentation. To find our class, find "java.io" in the list of packages in the upper left frame of the API documentation page. Click on that link to see the list of classes in that package appearing in the lower left frame. Find and click on the "FileWriter" class. Documentation for that class will appear in the main frame.
Just under the documentation title "Class FileWriter", you'll see a
little diagram of the inheritance hierarchy. FileWriter
is a subclass of OutputStreamWriter, which is in turn, a
subclass of Writer. That's good: the
sayHello(Writer) needs a Writer object as
input, and FileWriter is a kind of Writer.
So, all we need to do is figure out how in to instantiate it.
To do that, we page down to the "Constructor Summary" list, which
lists the signatures of the constructors supported by the
class. Fortunately for us, there is one that just takes a
String representing the name of the file. In our class,
HelloToFile the user is giving us the filename as an
argument, which comes to us as String. Now click on the
"FileWriter" link for that version of the constructor to see more
details. One of the things we learn when we do that is that the
constructor can throw an IOException if something goes
wrong.
Before we go back to our source code, hit your browser's back button
on the window showing the API documentation, and notice that
FileWriter has a constructor that takes a
File object. Remember this, when we think about our
extra credit.
Now, let's use the FileWriter constructor. The file name
we get from the user will be the first element in the
args variable.
public static void main(String[] args) {
Hello greeter = new Hello();
FileWriter out = new FileWriter(args[0]);
}
Note that we could have written the FileWriter line like
this:
Writer out = new FileWriter(args[0]);
That's because a variable of a generic type, like Writer,
can hold an object of a more specific type, like
FileWriter. That is, because FileWriter
is a kind of Writer, we're okay. In our case,
it makes no difference either way.
Note that we're now using at least one class from a package other than
java.lang. That means we need an import statement:
package nvoss.basicjava; import java.io.*;
or
package nvoss.basicjava; import java.io.FileWriter; import java.io.Writer; // if we made out a variable of type Writer
Either is fine.
Now all we have to do is pass our Writer to our
Hello:
public static void main(String[] args) {
Hello greeter = new Hello();
FileWriter out = new FileWriter(args[0]);
greeter.sayHello(out);
}
Done! But wait, you say. What if...
- we don't have permission to write to the file that the user gives us?
- the disk is full and has no room for our file?
- the user forgets to give us a filename?
Right. This exercise is about handling exceptions. Those first two
possibilities can occur while the file is being opened or written to,
so these errors will occur inside FileWriter. It lets
you know something bad happened by throwing an exception. Errors
associated with opening the file are thrown by the constructor in the
form of, as the documentation told us, an IOException.
Write errors will occur inside the sayHello(Writer) method:
the writer we passed in will throw an IOException to the
sayHello(Writer) method which throws it back up to our
main() method. How do we know? Because the definition
of the sayHello(Writer) method in Hello.java
says so with its throws clause:
public void sayHello(Writer out)
throws IOException
{
out.write(getGreeting()); // out might throw an exception here
out.write('\n'); // or here
out.flush(); // or here
}
So the proper thing to do is to enclose our code in a try-catch block:
try {
Writer out = new FileWriter(args[0]);
greeter.sayHello(out);
}
catch (Exception ex) {
}
I chose to catch a more generic Exception object, because
other exceptions, such as run-time exceptions that we're not
warned about, may get thrown as well.
So while we're at it, we'll handle the user error you mentioned by checking the length of the input argument array:
try {
if (args.length == 0)
throw new IllegalArgumentException("No filename given");
If you are looking for an appropriate exception to throw, you can
peruse the exceptions listed in the
API
documentation for the java.lang package (or whatever
package might be appropriate). Otherwise, you could just throw a
generic Exception object.
Now all we have to do is deal with these problems. A nice message to user might be helpful:
catch (Exception ex) {
System.err.println(ex.getMessage());
System.exit(1);
}
The exception, "caught" into the variable ex, carries a
descriptive message about what went wrong. We can pass that onto the
user. If we catch a more specific exception, like
FileNotFoundException (a type of
IOException), we could provide the user with a
friendlier, more context-specific error message. After printing the
message, we use a static method from the java.lang.System
class called exit() to exit the application with an error
code.
That should do it for the code--let's run it:
> javac -g HelloToFile.java > java nvoss.basicjava.HelloToFile hello.txt > more hello.txt Hello world! > java nvoss.basicjava.HelloToFile No filename given
Beautiful!
Finally, to get the extra credit, consult the
API
documentation for the File class: just click on the
"File" link in the FileWriter page we looked at earlier.
You'll find that it has methods like exists() and
canWrite() that you can use to do other checks on the
filename given to you by the user.
