Chapter:Exceptions - Code examples - Exercises

From Juneday education
Jump to: navigation, search

Exercises

1 - What could go wrong when trying to write to a file

Name three things which could go wrong when we open a file which we later want to write to. What do you think the corresponding exceptions are called? (This is not something you need to memorize for the exam - if this book is used in a course. Rather, we will encourage you to think about cases which can go wrong, and investigate the API to get hints about how it works!)

Hint: The FileOutputStream class might have some hints, regarding files - look at the constructors

Expand using link to the right to see a suggested answer/solution.

  • The file might exist but actually be a directory - not good ;-) - FileNotFoundException
  • The file cannot be created by us (filesystem/OS says NO!) - SecurityException
  • The file exists, but we cannot open it for some reason - SecurityException

Actually, the constructor for FileOutputStream(File f) will additionally throw a NullPointerException if the reference for the file object used in the constructor which takes a File reference is null. But this is a programmer error, and not a problem with opening files in general!

2 - The JavaReader

In this exercise, we'll have a small application which can read and print the contents of a java source code file. The main uses the class JavaReader (provided as source code) to create an object and call the readAndPrint() method on this object.

However, the readAndPrint() method calls some API method which throw IOExceptions, so we must fix this in order to be able to compile!

This is the source tree for the code in this example:

.
`-- org
    `-- corporation
        |-- files
        |   `-- JavaReader.java
        `-- main
            `-- Main.java

By now, it should be fairly clear to you that the application consists of two classes from two packages:

  • org.corporation.files.JavaReader
  • org.corporation.main.Main

It's your first task to create the classes as source code files in the correct directory structure. Start by creating the directories:

$ mkdir -p org/corporation/files
$ mkdir -p org/corporation/main

Next create the sourcode files (for instance by opening atom giving it the argument org/corporation/files/JavaReader.java for the first file, then creating the other file in a similar fashion in org/corporation/files/main/Main.java ). These are code listings of the classes:

1 package org.corporation.main;
2 import org.corporation.files.JavaReader;
3 
4 public class Main{
5   public static void main(String[] args){
6     JavaReader reader = new JavaReader("org/corporation/files/wrong.java");
7     reader.readAndPrint();
8   }
9 }

And:

JavaReader class diagram
 1 package org.corporation.files;
 2 import java.io.IOException;
 3 import java.io.BufferedReader;
 4 import java.nio.file.Files;
 5 import java.nio.file.Path;
 6 import java.nio.charset.Charset;
 7 import java.nio.file.FileSystems;
 8 
 9 public class JavaReader{
10   private String file;
11   public JavaReader(String file){
12     this.file = file;
13   }
14   public void readAndPrint(){
15     Path path = FileSystems.getDefault().getPath(file);
16     BufferedReader reader =
17       Files.newBufferedReader(path,Charset.forName("US-ASCII"));
18     String line = null;
19     while ((line = reader.readLine()) != null) {
20       System.out.println(line);
21     }
22   }
23 }
Information

Note: if we choose US-ASCII for "charset", we limit ourselves to only characters in the ascii table, including the latin letters of the English language.

If we want to be able to read a Java file, it would actually be smarter to use "UTF-8" as "charset", which uses characters from a much larger table. UTF-8 is also what Java accepts as encoding for source code files.

Try both charsets in combination with non-us-ascii characters to see what happens (if you want)

So, the main method creates an instance of org.corporation.files.JavaReader giving the constructor one argument "org/corporation/files/wrong.java". This is meant to create a JavaReader which knows the name and relative path of a Java-file.

Next, main tries to call readAndPrint() on the object just created. But the readAndPrint() won't be compiled, since the method uses calls which may throw an IOException. So the readAndPrint() must be changed somehow.

Compile the org/corporation/main/Main.java application.

$ javac org/corporation/main/Main.java

The compiler will say:

./org/corporation/files/JavaReader.java:19: error: unreported exception IOException; must be caught or declared to be thrown
      Files.newBufferedReader(path,Charset.forName("US-ASCII"));
                             ^
./org/corporation/files/JavaReader.java:21: error: unreported exception IOException; must be caught or declared to be thrown
    while ((line = reader.readLine()) != null) {
                                  ^
2 errors

Change the readAndPrint() method in org/corporation/files/JavaReader.java so that it declares that it throws IOException. Recompile.

What you just did, was to pass on the responsibility for the IOException to the caller of readAndPrint(), which is the main method. But the main method doesn't have a try-block with a handler for IOException. So the compiler will now complain:

org/corporation/main/Main.java:6: error: unreported exception IOException; must be caught or declared to be thrown
    reader.readAndPrint();
                       ^
1 error

Just as we suspected. Next, add a try-block in main, with a catch-block which catches an IOException. You will need to import java.io.IOException in the Main class for this to work. The following is a hint for how to write the try-catch handler in the main method:

try{
  // call the method here
}catch(IOException e){
  // Print an error message, print e (or ask e for the message it might have using e.getMessage() )
  // See the slides and video for examples
}

Compile again and confirm that compilation works.

Now run the program:

$ java org.corporation.main.Main

Note that there's an error message (because the code can't find the file called "org/corporation/files/wrong.java" (because there is no such file). If your handler in Main printed the reference to the exception, e, this is an example result:

Something went wrong: java.nio.file.NoSuchFileException: org/corporation/files/wrong.java

Expand using link to the right to see a suggested answer/solution.

Suggested source for JavaReader:

package org.corporation.files;
import java.io.IOException;
import java.io.BufferedReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.charset.Charset;
import java.nio.file.FileSystems;

public class JavaReader{
  private String file;
  public JavaReader(String file){
    this.file = file;
  }
  public void readAndPrint() throws IOException{
    Path path = FileSystems.getDefault().getPath(file);
    BufferedReader reader =
      Files.newBufferedReader(path,Charset.forName("US-ASCII"));
    String line = null;
    while ((line = reader.readLine()) != null) {
      System.out.println(line);
    }
  }
}

The suggested code for Main is (note the import statement of IOException):

package org.corporation.main;
import java.io.IOException;
import org.corporation.files.JavaReader;
public class Main{
  public static void main(String[] args){
    JavaReader reader = new JavaReader("org/corporation/files/wrong.java");
    try{
      reader.readAndPrint();
    }catch(IOException e){
      System.err.println("Something went wrong: " + e);
    }
  }
}

3 - The JavaReader - with chained (wrapped) RuntimeException

Now, we shall change the JavaReader's readAndPrint() method again, so that it doesn't say throws IOException. Instead, we will write a handler for IOException in the method itself, and throw a RuntimeException with the IOException inside it, together with a message.

Start by removing the throws IOException from readAndPrint(). Next, surround the code inside the method with a try-block which ends with a catch-block catching IOException. Call the IOException you catch e. Inside the catch-block, throw a new RuntimeException with the following two parameters in the call to the constructor: "Error reading file" and e. Something like:

...
...
catch(IOException e){
  throw new RuntimeException("Error reading file", e);
}

Next, remove the import statement in main which imports the java.io.IOException class. And change the catch-block so that it catches a RuntimeException instead (or even Exception if you want to catch all kinds of exceptions). In the catch-block, make the following printout to standard error:

   System.err.println("Critical failure: " + e.getMessage());
   System.err.println("Cause: " + e.getCause());

Compile your classes and run again. What was the output this time?

Expand using link to the right to see a suggested answer/solution.

Suggested new version of JavaReader:

package org.corporation.files;
import java.io.IOException;
import java.io.BufferedReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.charset.Charset;
import java.nio.file.FileSystems;

public class JavaReader{
  private String file;
  public JavaReader(String file){
    this.file = file;
  }
  public void readAndPrint(){
    try{
      Path path = FileSystems.getDefault().getPath(file);
      BufferedReader reader =
	Files.newBufferedReader(path,Charset.forName("US-ASCII"));
      String line = null;
      while ((line = reader.readLine()) != null) {
	System.out.println(line);
      }
    }catch(IOException e){
      throw new RuntimeException("Error reading file", e);
    }
  }
}

Suggested new version of Main:

package org.corporation.main;
import org.corporation.files.JavaReader;
public class Main{
  public static void main(String[] args){
    JavaReader reader = new JavaReader("org/corporation/files/wrong.java");
    try{
      reader.readAndPrint();
    }catch(Exception e){
      System.err.println("Critical failure: " + e.getMessage());
      System.err.println("Cause: " + e.getCause());
    }
  }
}

Note that main doesn't have to import java.io.IOException any more. It handles any exception (or if you chose to catch RuntimeException, any runtime exception) instead. We have less coupling between Main and JavaReader, but we can still catch critical exceptions.

The output will now be:

$ javac org/corporation/main/Main.java && java org.corporation.main.Main
Critical failure: Error reading file
Cause: java.nio.file.NoSuchFileException: org/corporation/files/wrong.java

4 - Fix the file name

Now, change the filename in the constructor call to JavaReader to take the argument of a correct file which exists:

JavaReader reader = new JavaReader("org/corporation/files/JavaReader.java");

Re-compile and run again. What was the output from the program?

Expand using link to the right to see a suggested answer/solution.

The output should be the source code for org/corporation/files/JavaReader.java !

Links

Source code

  • Code from presentations and solutions (github)

Further reading

Where to go next

The next page is: Exceptions_-_Creating_your_own

« PreviousBook TOCNext »