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:

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...

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.