Object-Oriented Programming with Java, part I + II

cc

This material is licensed under the Creative Commons BY-NC-SA license, which means that you can use it and distribute it freely so long as you do not erase the names of the original authors. If you make changes in the material and want to distribute this altered version of the material, you have to license it with a similar free license. The use of the material for commercial use is prohibited without a separate agreement.

Authors: Arto Hellas, Matti Luukkainen
Translators to English: Emilia Hjelm, Alex H. Virtanen, Matti Luukkainen, Virpi Sumu, Birunthan Mohanathas, Etiënne Goossens
Extra material added by: Etiënne Goossens, Maurice Snoeren, Johan Talboom

The course is maintained by De Haagse Hogeschool


7. Input/output and exception handling

7.1 Exceptions

Exceptions are such situations where the program executions is different from our expectations. For instance, the program may have called a method of a null reference, in which case the user is thrown a NullPointerException. If we try to retrieve a index outside a table, the user is thrown a IndexOutOfBoundsException. All of them are a type of Exception.

Exceptions are often caused by unexpected situations that occur when handling input and output of a program. For example: users can easily frustrate a program by entering input in an unexpected format.

We deal with exceptions by using the block try { } catch (Exception e) { }. The code contained within the brackets which follows the keyword try will not crash when an exception occurs in this part of the code. The code within the brackets which follow the keyword catch defines what should happen when the try-code throws an exception. We also define the type of the exception we want to catch (catch (Exception e)).

try {
    // code which can throw an exception
} catch (Exception e) {
    // code which is executed in case of exception
}

The parseInt method of class Integer which turns a string into a number can throw a NumberFormatException if its string parameter cannot be turned into a number. Now we implement a program that turns a user input String to a number.

Scanner reader = new Scanner(System.in);
System.out.print("Write a number: ");

int num = Integer.parseInt(reader.nextLine());
Write a number: ~~tatti~~
~~Exception in thread "..." java.lang.NumberFormatException: For input string: "tatti"~~

The program above throws an exception because the user digits an erroneous number. The program execution ends up with a malfunction, and it cannot continue. We add an exception management statement to our program. The call, which may throw an exception is written into the try block, and the action which takes place in case of exception is written into the catch block.

Scanner reader = new Scanner(System.in);

System.out.print("Write a number: ");

try {
    int num = Integer.parseInt(reader.nextLine());
} catch (Exception e) {
    System.out.println("You haven't written a proper number.");
}
Write number: ~~5~~
Write number: ~~oh no!~~
You haven't written a proper number.

In case of exception, we move from the chunk of code defined by the try keyword to the catch chunk. Let’s see this by adding a print statement after the Integer.parseInt line in the try chunk.

Scanner reader = new Scanner(System.in);

System.out.print("Write a number: ");

try {
    int num = Integer.parseInt(reader.nextLine());
    System.out.println("Looks good!");
} catch (Exception e) {
    System.out.println("You haven't written a proper number.");
}
Write a number: ~~5~~
Looks good!
Write a number: ~~I won't!~~
you haven't written a proper number.

String I won’t! is given as parameter to the method Integer.parseInt, which throws an exception if the String parameter can’t be changed into a number. Note that the code in the catch chunk is executed only in case of exception – otherwise the program do not arrive till there.

Let’s make something more useful out of our number translator: let’s do a method which keeps on asking to type a number till the user does it. The user can return only if they have typed the right number.

public int readNumber(Scanner reader) {
    boolean running = true;
    while (running) {
        System.out.print("Write a number: ");

        try {
            int num = Integer.parseInt(reader.nextLine());
            return num;
        } catch (Exception e) {
            System.out.println("You haven't written a proper number.");
        }
    }
}

The method readNumber could work in the following way:

Write a number: ~~I won't!~~
You haven't written a proper number.
Write a number: ~~Matti has a mushroom on his door.~~
You haven't written a proper number.
Write a number: ~~43~~

7.1.1 Throwing Exceptions

Methods and constructors can throw exceptions. So far, there are two kinds of exceptions which can be thrown. There are the ones which have to be handled, and the ones which don’t have to be dealt with. When we have to handle the exceptions, we do it either in a try-catch chunk, or throwing them from a method.

In the clock exercise of Introduction to Programming (not in the HHS-course), we explained that we can stop our program of one second, by calling the method Thread.sleep(1000). The method may throw an exception, which we must deal with. In fact, we handle the exception using the try-catch sentence; in the following example we skip the exception, and we leave empty the catch chunk.

try {
    // we sleep for 1000 milliseconds
    Thread.sleep(1000);
} catch (Exception e) {
    // In case of exception, we do not do anything.
}

It is also possible to avoid handling the exceptions in a method, and delegate the responsibility to the method caller. We delegate the responsibility of a method by using the statement throws Exception.

public void sleep(int sec) throws Exception {
    Thread.sleep(sec * 1000);   // now we don't need the try-catch block
}

The sleep method is called in another method. Now, this other method can either handle the exception in a try-catch block or delegate the responsibility forward. Sometimes, we delegate the responsibility of handling an exception till the very end, and even the main method delegates it:

public class Main {
   public static void main(String[] args) throws Exception {
       // ...
   }
}

In such cases, the exception ends up in Java’s virtual machine, which interrupts the program in case there is an error which causes the problem.

There are some exceptions which the programmer does not always have to address, such as the NumberFormatException which is thrown by Integer.parseInt. Also the RuntimeExceptions do not always require to be addressed. These are two examples of exceptions that don’t have to be dealt with. The compiler cannot ‘predict’ that an exception will occur, so it does not force you to catch the exception.

We can throw an exception by using the throw statement. For instance, if we want to throw an exception which was created in the class NumberFormatException, we could use the statement throw new NumberFormatException().

Another exception which hasn’t got to be addressed is IllegalArgumentException. With IllegalArgumentException we know that a method or a constructor has received an illegal value as parameter. For instance, we use the IllegalArgumentException when we want to make sure that a parameter has received particular values.

In the example below we have the constructor public Grade(int grade). A constructor is a special method that will be explained later on when we introduce classes. For now you can consider it as a ‘regular’ method that expects parameters like any other method.

public class Grade {
    private int grade;

    public Grade(int grade) {
        this.grade = grade;
    }

    public int getGrade() {
        return this.grade;
    }
}

Next, we want to validate the value of the constructor parameter of our Grade class. The grades in Finland are from 0 to 5. If the grade is something else, we want to throw an exception. We can add an if statement to our Grade class constructor, which checks whether the grade is outside range 0-5. If so, we throw an IllegalArgumentException telling throw new IllegalArgumentException("The grade has to be between 0-5");.

public class Grade {
    private int grade;

    public Grade(int grade) {
        if (grade < 0 || grade > 5) {
            throw new IllegalArgumentException("The grade has to be between 0-5");
        }
        this.grade = grade;
    }

    public int getGrade() {
        return this.grade;
    }
}
Grade grade = new Grade(3);
System.out.println(grade.getGrade());

Grade wrongGrade = new Grade(22);
// it causes an exception, we don't continue
3
Exception in thread "..." java.lang.IllegalArgumentException: The grade has to be between 0-5

7.1.2 The Exception Information

The catch block tells how we handle an exception, and it tells us what exception we should be prepared for: catch (Exception e). The exception information is saved into the e variable.

try {
    // the code, which may throw an exception
} catch (Exception e) {
    // the exception information is saved into the variable e
}

The class Exception can provide useful methods. For instance, the method printStackTrace() prints a path which tells us where the exception came from. Let’s check the following error printed by the method printStackTrace().

Exception in thread "main" java.lang.NullPointerException
  at package.Class.print(Class.java:43)
  at package.Class.main(Class.java:29)

Reading the stack trace happens button up. The lowest is the first call, i.e. the program execution has started from the main() method of class Class. At line 29 of the main method of Class, we called the method print(). Line 43 of the method print caused a NullPointerException. Exception information are extremely important to find out the origin of a problem.

Exercise 5-1: Method Argument Validation

Let’s train method argument validation with the help of the IllegalArgumentException. The excercise layout shows two classes Person and Calculator. Change the class in the following way:

Exercise 5-1.1: Person Validation

The constructor of Person has to make sure its parameter’s name variable is not null, empty, or longer than 40 characters. The age has also to be between 0-120. If one of the conditions above are not satisfied, the constructor has to throw an IllegalArgumentException.

Exercise 5-1.2: Calculator Validation

The Calculator methods have to be changed in the following way: the method multiplication has to work only if its parameter is not negative (greater than or equal to 0). The method binomialCoefficient has to work only if the parameters are not negative and the size of a subset is smaller than the set’s size. If one of the methods receives invalid arguments when they are called, they have to throw a IllegalArgumentException.

Exercise 5-1.3: Exception handling

The main method crashes when an IllegalArgumentException is thrown. Add proper exception handling so that the main method does not crash any more. Print the exception information, so that the program shows what goes wrong. NB: This part of the exercise is not verified automatically.

7.2 Reading from a file

A relevant part of programming is related to stored files, in one way or in another. Let’s take the first steps in Java file handling. Java’s API provides the class File, whose contents can be read using the already known Scanner class.

If we read the desciption of the File API we notice the File class has the constructor File(String pathname), which creates a new File instance by converting the given pathname string into an abstract pathname. This means the File class constructor can be given the pathname of the file we want to open.

In the programming environment, files have got their own tab called Files, which contains all our project files. If we add a file to a project root – that is to say outside all folders – we can refer to it by writing only the its name. We create a file object by giving the file pathname to it as parameter:

File file = new File("file-name.txt");

The System.in input stream is not the only reading source we can give to the constructor of a Scanner class. For instance, the reading source can be a file, in addition to the user keyboard. Scanner provides the same methods to read a keyboard input and a file. In the following example, we open a file and we print all the text contained in the file using the System.out.println statement. At the end, we close the file using the statement close.

// The file we read
File file = new File("filename.txt");

Scanner reader = new Scanner(file);
while (reader.hasNextLine()) {
    String line = reader.nextLine();
    System.out.println(line);
}

reader.close();

The Scanner class constructor public Scanner(File source) (Constructs a new Scanner that produces values scanned from the specified file.) throws a FileNotFoundException when the specified file is not found. The FileNotFoundException is different than RuntimeException, and we have either to handle it or throw it forward. At this point, you only have to know that the programming environment tells you whether you have to handle the exception or not. Let’s first create a try-catch block where we handle our file as soon as we open it.

public void readFile(File f) {
    // the file we read
    Scanner reader = null;

    try {
        reader = new Scanner(f);
    } catch (Exception e) {
        System.out.println("We couldn't read the file. Error: " + e.getMessage());
        return; // we exit the method
    }

    while (reader.hasNextLine()) {
        String line = reader.nextLine();
        System.out.println(line);
    }

    reader.close();
}

Another option is to delegate the exception handling responsibility to the method caller. We delegate the exception handling responsibility by adding the definition throws ExceptionType to the method. For instance, we can add throws Exception because the type of all exceptions is Exception. When a method has the attribute throws Exception, whatever chunk of code which calls that method knows that it may throw an exception, and it should be prepared for it.

public void readFile(File f) throws Exception {
    // the file we read
    Scanner reader = new Scanner(f);

    while (reader.hasNextLine()) {
        String line = reader.nextLine();
        System.out.println(line);
    }

    reader.close();
}

In the example, the method readFile receives a file as parameter, and prints all the file lines. At the end, the reader is closed, and the file is closed with it, too. The attribute throws Exception tells us that the method may throw an exception. Same kind of attributes can be added to all the methods that handle files.

Note that the Scanner object’s method nextLine returns a string, but it does not return a new line at the end of it. If you want to read a file and still maintain the new lines, you can add a new line at the end of each line:

public String readFileString(File f) throws Exception {
    // the file we read
    Scanner reader = new Scanner(f);

    String string = "";

    while (reader.hasNextLine()) {
        String line = reader.nextLine();
        string += line;
        string += "\n";
    }

    reader.close();
    return string;
}

Because we use the Scanner class to read files, we have all Scanner methods available for use. For instance the method hasNext() returns the boolean value true if the file contains something more to read, and the method next() reads the following word and returns a String object.

The following program creates a Scanner object which opens the file file.txt. Then, it prints every fifth word of the file.

File f = new File("file.txt");
Scanner reader = new Scanner(f);

int whichNumber = 0;
while (reader.hasNext()) {
    whichNumber++;
    String word = reader.next();

    if (whichNumber % 5 == 0) {
        System.out.println(word);
    }
}

Below, you find the text contained in the file, followed by the program output.

Exception handling is the process of responding to the occurrence, during computation, of exceptions – anomalous or exceptional events 
 requiring special processing – often changing the normal flow of program execution. ...
process
occurrence,
–
requiring
changing
program

7.2.1 Character Set Issues

When we read a text file (or when we save something into a file), Java has to find out the character set used by the operating system. Knowledge of the character set is required both to save text on the computer harddisk in binary format, and to translate binary data into text.

There have been developed standard character sets, and “UTF-8” is the most common nowadays. UTF-8 character set contains both the alphabet letters of everyday use and more particular characters such as the Japanese kanji characters or the information need to read and save the chess pawns. From a simplified programming angle, we could think a character set both as a character-number hashmap and a number-character hashmap. The character-number hashmap shows what binary number is used to save each character into a file. The number-character hashmap shows how we can translate into characters the values we obtain reading a file.

Almost each operating system producer has also got their own standards. Some support and want to contribute to the use of open source standards, some do not. If you have got problems with the use of Scandinavian characters such as ä and ö (expecially Mac and Windows users), you can tell which character set you want to use when you create a Scanner object. In this course, we always use the the “UTF-8” character set.

You can create a Scanner object which to read a file which uses the UTF-8 character set in the following way:

File f = new File("examplefile.txt");
Scanner reader = new Scanner(f, "UTF-8");

Anther thing you can do to set up a character set is using an environment variable. Macintosh and Windows users can set up an the value of the environment variable JAVA_TOOL_OPTIONS to the string -Dfile.encoding=UTF8. In such case, Java always uses UTF-8 characters as a default.

Exercise 5-2: Printer

Fill in the gaps in the class Printer

It has a constructor public Printer(String t) which receives a String standing for the file name. It opens the file, reads all the lines and puts them into an ArrayList.

The method public void printLinesWhichContain(String word) loops through the ArrayList and prints the lines that contain the word that is passed as a parameter. The lines are printed in the same order as they are inside the file. (lower and upper case make difference in this excercise; for instance, “test” and “Test” are not the considered equal);

If the argument is an empty String, all of the file is printed.

If the file is not found, the method delegates the exception with no need for a try-catch statement; the method simply has to be defined in the following way:

public Printer {

  public Printer(String t) throws Exception {
     // ...
  }

  // ...
}

The file textFile has been place into the default package of your project to help the tests. When you define the file name of the constructor of Printer, you have to write src/textfile.txt. The file contains an extract of Kalevala, a Finnish epic poem:

Siinä vanha Väinämöinen
katseleikse käänteleikse
Niin tuli kevätkäkönen
näki koivun kasvavaksi
Miksipä on tuo jätetty
koivahainen kaatamatta
Sanoi vanha Väinämöinen

The following example shows what the program should do:

Printer printer = new Printer("src/textfile.txt");

printer.printLinesWhichContain("Väinämöinen");
System.out.println("-----");
printer.printLinesWhichContain("Frank Zappa");
System.out.println("-----");
printer.printLinesWhichContain("");
System.out.println("-----");

Prints:

Siinä vanha Väinämöinen
Sanoi vanha Väinämöinen
-----
-----
Siinä vanha Väinämöinen
katseleikse käänteleikse
Niin tuli kevätkäkönen
näki koivun kasvavaksi
Miksipä on tuo jätetty
koivahainen kaatamatta
Sanoi vanha Väinämöinen

In the project, you also find the whole Kalevala; the file name is src/kalevala.txt

Exercise 5-3: File Analysis

In this exercise, we create an application to calculate the number of lines and characters.

Implement the method readFile(). Read the file using a scanner and append all the lines to the variable ‘str’. Add a ‘new line’ character at then end of every line.

You can decide yourself what to do if the constructor parameter file does not exist.

The file testFile has been place into the test package of your project to help the tests. When you define the file name of the constructor of Analysis, you have to write test/testfile.txt. The file contains the following text:

there are 3 lines, and characters
because line breaks are also
characters

The following example shows what the program should do:

File file = new File("test/testfile.txt");
Analysis analysis = new Analysis(file);
System.out.println("Lines: " + analysis.lines());
System.out.println("Characters: " + analysis.characters());
Lines: 3
Characters: 74

7.3 Writing a File

In section 12.1, we learnt that reading from a file happened with the help of the classes Scanner and File. The class FileWriter provides the functionality to write to a file. The FileWriter constructor is given as parameter a String illustrating the file location.

FileWriter writer = new FileWriter("file.txt");
writer.write("Hi file!\n"); // the line break has to be written, too!
writer.write("Adding text\n");
writer.write("And more");
writer.close(); // the call closes the file and makes sure the written text goes to the file

In the example we write the string “Hi file!” to the file “file.txt”; that is followed by a line break, and by more text. Note that when you use the write method, it does not produce line breaks, but they have to be added later manually.

Both the FileWriter constructor and the write method may throw an exception, which has to be either handled or the responsibility has to be delegated to the calling method. The method which is given as parameter the file name and the text to write into it can look like the following.

public class FileHandler {

    public void writeToFile(String fileName, String text) throws Exception {
        FileWriter writer = new FileWriter(fileName);
        writer.write(text);
        writer.close();
    }
}

In the above method writeToFile, we first create a FileWriter object, which writes into the fileName file stored at the location specified as parameter. After this, we write into the file using the write method. The exception the constructor and write method can possibly throw has to be handled either with the help of a try-catch block or delegating the responsibility. In the method writeToFile the responsibility was delegated.

Let’s create a main method where we call the writeToFile method of a FileHandler object. The exception does not have to be handled in the main method either, but the method can declare to throw possibly an exception throw the definition throws Exception.

public static void main(String[] args) throws Exception {
    FileHandler handler = new FileHandler();
    handler.writeToFile("diary.txt", "Dear Diary, today was a nice day.");
}

When we call the method above, we create the file “diary.txt”, where we write the text “Dear Diary, today was a nice day.”. If the file exists already, the old content is erased and the new one is written. FileWriter allows us to add text at the end of the already existing file by providing additional parameter boolean append, without erasing the existing text. Let’s add the method appendToFile() to the class FileHandler; the method appends the text received as parameter to the end of the file.

public class FileHandler {
    public void writeToFile(String fileName, String text) throws Exception {
        FileWriter writer = new FileWriter(fileName);
        writer.write(text);
        writer.close();
    }

    public void appendToFile(String fileName, String text) throws Exception {
        FileWriter writer = new FileWriter(fileName, true);
        writer.write(text);
        writer.close();
    }
}

In most of the cases, instead of writing text at the end of a file with the method append, it is easier to write all the file again.

Exercise 5-4: File Manager

Together with the exercise body, you find the class FileManager, which contains the method bodies to read a write a file.

Exercise 5-4.1: File Reading

Implement the method public ArrayList<String> read(String file) to return the lines of the parameter file in ArrayList form, each file line being a String contained by the ArrayList.

There are two text files to help testing the project: src/testinput1.txt and src/testinput2.txt. The methods are supposed to be used in the following way:

public static void main(String[] args) throws FileNotFoundException, IOException {
    FileManager f = new FileManager();

    for (String line : f.read("src/testinput1.txt")) {
        System.out.println(line);
    }
}

The print output should look like the following

first
second

Exercise 5-4.2: Writing a Line

Modify the method public void save(String file, String text) so that it writes the String text into the file. If the file already exists, overwrite its contents.

Exercise 5-4.3: Writing an ArrayList

Modify the method public void save(String file, ArrayList<String> texts) so that it writes the content of the ArrayList texts into the file. If the file already exists, overwrite its contents.


end of week 5