1.3-Object-Georienteerd-Programmeren

Exceptions and how to use Javadoc

Competence: I can use checked and unchecked exceptions (including custom created exceptions) and can explain the difference between the two.

Competence: I can use the official Java specification.

Introduction

So far, to handle problems in your own application, you have used the classes IllegalStateException and IllegalArgumentException whenever something “went wrong”. You used these exceptions in places to indicate that something unexpected happened (e.g., you try to take off with an airplane that has open doors) or that a value didn’t make any sense (e.g., a negative age).

By now you’ve also seen other examples of exceptions, such as the FileNotFoundException which is thrown when you try to open a file that does not exist. What you may also have noticed is that this exception always has to be caught, while with the earlier mentioned IllegalStateException and IllegalArgumentException this was not the case: you may catch these exceptions, but this was not required. There is difference between exceptions and why and when you need to handle them. How exceptions work will be discussed in more detail this week.

What we also didn’t discuss yet is what you do when you’ve found an error that is so severe that an exception is not enough. Sometimes you want to send out a message that something is really going so terribly wrong that it can’t be fixed anymore inside the application and your program is going to crash. In this case, you want to throw a real “error” rather than an exception. Java already does this in several places: for example, if your computer’s memory is full (OutOfMemoryError). We will discuss today what exactly the difference is between an exception and an error and how you can use this inside your own programs.

Finally, we want to teach you where you can get your own information about classes you might not have seen yet (apart from these files!). The past few programming courses (Introduction to Programming and this course) we often explained how certain classes work. But since we did not create all classes ourselves, we (as teachers) also had to look this up at some point. All this knowledge is fortunately contained within the official Java (API) documentation. In previous assignments / weeks, we have sometimes already referred you to these pages, but in some cases also (deliberately) not: This is mainly because the pages in the official documentation are sometimes so technically written, that they are practically unreadable for the novice programmer. At this point in your programming journey however, you’ve seen everything (static, non-static, abstract, final, methods, constructors, interfaces, etc.) you need to properly understand this documentation. (In short: You are ready!) And so we end this course with a brief lesson on “how to find out what class I need and what that class does” (within Java).

However, we’ll start with the topic of error handling!

Error handling

As you already know, exceptions are classes (in Java and other languages) that are used to handle problems in your program. In short, you can throw an exception if something goes wrong.

The fact that you can throw an exception (i.e. use it in conjunction with the throws keyword) is due to the fact that the class Exception (and all subclasses of Exception) extend the class Throwable. And anything that extends the class Throwable may be thrown with the keyword throw.

Throwable objects are used exclusively to handle problems in your application. As soon as you throw an exception, the normal flow of your software is interrupted and the problem must be fixed (caught) before the program can continue.

The Exception class is the most well-known Throwable implementation within Java. However, there is another class that extends the Throwable class that we have not yet discussed, the Error class.

Errors

An Error object is thrown the moment your program breaks in such a way that recovery is no longer possible. You may have seen such an error before, such as the OutOfMemoryError when your computer runs out of memory or a StackOverflowError if you make too many stacked (recursive) method calls in your code. (Perhaps a runaway loop?)

The big difference between an Error and an Exception is that with an error you want to indicate that something happened that cannot be fixed. So your program will (most likely) crash.

Within this course, you don’t need to write your own Error classes. It is good to know that there are Error classes and that you can treat them in the same way as Exceptions, but creating your own Error classes is rare.

Checked en unchecked exceptions

As described earlier, there are Exceptions where you are required to put a try { ... } catch(..) block around and there are Exceptions where you do not have to do this. Take for example the reading of a file: While trying to read a file you can expect a FileNotFoundException, which will be thrown if the file cannot be found at the specified location (or cannot be opened). If you try to read a file (e.g. with the Scanner), you must put that code in a try { ... } catch (..) block. If you don’t do this, your program will not compile.

This is different from throwing IllegalStateExceptions and IllegalArgumentExceptions as we have been doing since week 2. This is because these last two exceptions are subclasses of the RuntimeException class. Runtime in this case means that you find out during the execution of the program that something is not correct. We have used these kinds of exceptions so far to indicate to methods (or constructors) that certain values were “unexpected” or that a method was called at a time when it should not have been. Both things we cannot always check before we run our application.

And exactly this last point is where it sometimes gets complicated: What can you expect in terms of checking and what can’t you expect? When do you know when you can or cannot execute a method and what values are accepted?

In most programming languages, this problem is solved by making a clear distinction between checked and unchecked exceptions. Checked exceptions are exceptions that you can logically expect (such as a file that cannot be found, a network connection that fails, etc.) and unchecked exceptions are exceptions that really entirely depend on what possibly might happen during the execution of your program (e.g., you read a piece of data from a file and that data contains a character you can’t parse).

Most of the exceptions you will use while programming are checked exceptions. And this means you have to figure out how to deal with them, even if they are not actually thrown. You’ve already seen the biggest difference with regards to using check exceptions: They have to be handled with a try { ... } catch (..) block while unchecked exceptions do not require this.

If you want to know if a certain method call will produce either a checked or unchecked exception the best way is reading the documentation.

Suppose we want to do the following.

Scanner sc = new Scanner(new File("demo.csv"));

If we then open the documentation for the Scanner class and look for the constructor used in the example above, we will find the following information:

Scanner file not found

Especially the first line (with the methog signature) is important here: The constructor public Scanner(File source) throws FileNotFoundException indicates that upon initialization, the constructor may throw a FileNotFoundException if the file cannot be found or opened.

The class Scanner expects the file “demo.csv” to exist and be readable. If this isn’t the case, this instance of the Scanner class has become useless (you can’t do anything useful with it). However, it makes sense to expect that a file does not exist or cannot be read (typo in the file name, no rights, etc.). And so the class Scanner states that the checked exception FileNotFoundException may be thrown if something goes wrong here. And since that chance exists you will therefore be obligated to write code to resolve that situation should it occur. (The use of a checked exception forces you to to come up with a solution for this kind of problem!)

By far the most classes that inherit from Exception in Java are examples of checked exceptions. And this is considered to be a good practice: If you create a class that really depends on something being correct, initialized, etc., make sure you point out all the problems that you can reasonably expect to encounter when using that class and throw checked exceptions when they occur. This way, it should be very clear for other programmers how to use your class / method and what exceptions they can expect while using your work.

A total overview of exceptions

Because we can imagine that you might get lost track of when to do what, we have created a “cheat sheet”. Study the image carefully.

Throwable overview

Let’s create our own exception!

Creating an Exception

Creating your own Exception is easier than you might think: You simply inherit from the class Exception and call the appropriate constructor of the class Exception. Let’s make an AirplaneException class that we can use in the Airplane program you have created a few times by now. This exception will belong exclusively to this program and will focus on exceptions specifically associated with our program.

We create this exception in the following way:

public class AirplaneException extends Exception {
    public AirplaneException(String message) {
        super(message);
    }
}

(The class Exception has a constructor to which you can pass a message (String). We will come back to this when we start reading the official Java documentation).

The use of the class AirplaneException differs slightly from what you are used to so far, because we made AirplaneException a checked exception. Suppose we want to use our own exception in the depart() method (instead of the IllegalStateException as we have done now), then this looks like this:

public class Airplane {
    // Omitted a lot of code..
    
    public void depart() throws AirplaneException {
        if(!lockedDoors) {
            throw new AirplaneException("Doors must be locked before departure!");
        }
    }
}

(We used the depart() method to make a plane take off. This can only be done if the doors of the plane are closed!)

Notice that the depart() method now has an additional few words in its signature indicating that this method can throw an AirplaneException. This is mandatory for all checked exceptions as it ensures that in places where you call the depart() method you now must provide this code with a try { ... } catch (..) block.

Airplane airplane = new Airplane(..);

try {
    airplane.depart();
} catch (AirplaneException ae) {
    System.err.println(ae.getMessage());    
}

By the way, it is really unusual to create a RuntimeExceptions yourself. Normally you use the RuntimeExceptions that are already present in Java by default. There is usually no need for it and you normally want to force developers to deal with your exceptions. In fact: You can`t ever force a try-catch block around an unchecked exception. (So use it carefully!)

The code below therefor does nothing:

public void depart() throws IllegalStateException { // You don't need to add the "throws" part.. RuntimeExceptions cannot be "forced" to be caught.
    if(!lockedDoors) {
        throw new IllegalStateException("Doors must be locked before departure!");
    }
}

“Passing on” exceptions

Sometimes you don’t want to handle an exception directly in the method where it appears, but you just want to pass the exception to the calling method so it knows that “something went wrong”.

Consider the following code:

public class UserManager {

    private ArrayList<User> listOfUsers;

    public static void main(String[] args) {
        UserManager um = new UserManager("source-with-users.csv");
    }

    // constructor
    public UserManager(String fileName) {
        listOfUsers = loadUsersFromFile(fileName);
    }

    private ArrayList<User> loadUsersFromFile(String fileName) {
        ArrayList<User> result = new ArrayList<>();

        try {
            Scanner sc = new Scanner(new File(fileName));

            // Do parse stuff from data in that file

            sc.close();
        } catch (FileNotFoundException fnfe) { // We can expect all kinds of exceptions! FileNotFound, NumberFormat (from parsing), etc. 
            System.err.println(fnfe.getMessage());
        } catch (NumberFormatException nfe) {
            System.err.println("Unable to convert value: " + nfe.getMessage());
        }
        
        return result;
    }

}

Imagine that the class UserManager is based entirely on the data that is read from a file and is completely useless if this does not go well. If at any moment something goes wrong during the reading process, for example when opening the file or while parsing the data from the file, all possible errors in the method loadUsersFromFile will be handled right there. For any exceptions during parsing, this may make sense: you can e.g. just skip the user and continue with the next line from the file. But if the whole file can’t be found or opened, then really something is wrong.

In that case, it makes more sense to throw the FileNotFoundException to the constructor where the method loadUsersFromFile is called. This way, the constructor also is informed that something went wrong. However, in this example there is no reason for the constructor as well to do anything about it either: and so it makes more sense to also pass the FileNotFoundException to the code where the constructor is called. (In this case this is the code in the main method.)

In this way, by throwing a specific exception we ensure that the right exception is passed to the right place.

If we want to make these changes to the code, it will look like this:

public class UserManager {

    private ArrayList<User> listOfUsers;

    public static void main(String[] args) {
       try {
            UserManager um = new UserManager("source-with-users.csv");
        } catch (FileNotFoundException fnfe) {
            // Deal with it properly!
        }
    }

    // constructor
    public UserManager(String fileName) throws FileNotFoundException { // Any FileNotFoundExceptions thrown in the constructor, will be returned to the main method.
        listOfUsers = loadUsersFromFile(fileName);
    }

    public ArrayList<User> loadUsersFromFile(String fileName) throws FileNotFoundException { // We cannot deal with this exception here! Let's throw it back to the constructor.
        ArrayList<User> result = new ArrayList<>();

        Scanner sc = new Scanner(new File(fileName)); // This does not have to be inside the try-catch anymore, since we are passing on the exception to the constructor.

        try {
            // Do parse stuff from data in that file
        } catch (NumberFormatException nfe) { // Note that we are NO LONGER catching FileNotFoundException here!
            System.err.println("Unable to convert value: " + nfe.getMessage());
        }
        
        sc.close();
        
        return result;
    }
}

This way you can decide when you want to handle an error (as long as you do it once). You can even choose to let the main method pass exceptions (throw them towards your OS), but since this is guaranteed to cause your program to crash, that really is an example of a bad practice: All Exceptions should be handled somehow!

Try-catch… finally!

The above example introduces however another problem: As you learned last week, you should “close” files (and other external resources like e.g. Internet pages) after you’re done reading them (to make sure the connection is properly closed). However, if a parse error occurs in the UserManager this will not happen. This is because the creation of the Scanner instance is no longer inside the try { ... } catch (..) block! So the file you open will remain open even if an error occurred. And that is almost always undesirable.

However, you can easily solve this problem by using the keyword finally. With finally, you can enter a try { ... } catch (..) block to indicate what code should be executed after an exception is handled. In this case, you could include a piece of code that will neatly close the file, regardless of whether there were any errors during parsing or not. (It will execute regardless whether an exception was thrown, or not.)

This looks like this:

 public ArrayList<User> loadUsersFromFile(String fileName) throws FileNotFoundException { 
        ArrayList<User> result = new ArrayList<>();

        Scanner sc = new Scanner(new File(fileName));

        try {
            // Do parse stuff from data in that file
        } catch (NumberFormatException nfe) {
            System.err.println("Unable to convert value: " + nfe.getMessage());
        } finally { // Note that you can just put it underneath your catch blocks! It should be the "last block", similar to the else-statement in an if-else-if-etc. block.
            sc.close(); // After handling the previously mentioned exception, close the Scanner.
        }
        
        return result;
    }

You will see the word finally often used in situations where external resources (files, websites, etc.) are opened. You can read more about this in the documentation of these classes. This week we will also (somewhat) practice with this.

The use of finally however comes with some difficulty. It makes your code harder to understand when the code in your finally block is executed. (It is always after handling the exception, but you don’t know which method might do that.) That is why for now we’ll leave this topic as is: you’ve been shown the basics and know what is possible and that is more than enough for this course!

Java Development Kit API Specification (a.k.a. “the javadoc”)

The last part of the courses consists of learning how to read the official Java Development Kit API Specification. Over the past few weeks, we’ve regularly referred you to the official documentation, but this was always extra. Today we will discuss what you can actually find in this documentation and how you can best use these pages. We will therefore focus mainly on the practical application of this documentation.

Please note: With the introduction of intelligent programming environments like IntelliJ, it has become much easier to see what functionality a particular class has. However, IntelliJ still does not provide “the complete overview”: when should you use what class and how does a particular class work internally are questions that are never answered. This can only be obtained from reading the official documentation. Learning to use the official documentation properly (regardless of programming language) is therefore an important skill that you must master as a programmer!

In the sections below, we will dissect an entire page for you so you know what to focus on while reading. For our convenience, we will use as example the class that you have used by far the most during your programming classes: the class String.

We encourage you, while reading the information below, to also open the official page in a browser so that you can see our examples in real life as well. The easiest way to do this is to just Google for: “java String 17” (the last part ensures that you have the correct version of Java).

If all goes well you should be here: Oracle description String class

For practice, we also recommend that you keep a different class next to it, so you can practice reading these pages. An interesting example that you can use here is e.g. the class ArrayList. Especially focus on the similarities between the different pages.

In this text however, we will focus on the class String.

The location of the class in the entire Java hierarchy

As you know by now, inheritance is one of the most important parts of object-oriented programming and Java makes extensive use of this. However, it is sometimes difficult to see exactly what the supertypes of a particular class are and whether these classes implement certain interfaces.

Fortunately, this is always listed at the top of the page:

String class definition

From the image above we learn that a String inherits exclusively from the class java.lang.Object (which all classes in Java inherit from), the interfaces Serializable (not discussed in this course), CharSequence (also not), and Comparable<String> (this should sound familiar!) are implemented and the class belongs to the java.lang package.

Any properties, such as final or abstract can also be found here. Also, if the class is actually an interface it will be shown here. In case a class has already been used as a base class (i.e. is a superclass for other classes), the subclasss will be listed here as well. (Have a look at Map for example!)

Next is the official description of the class String, including examples of use. For now, let’s skip the explanation and scroll down a little further to “Field summary”.

Field summary

The heading “Field summary” contains all public static and non-static attributes (variables, not methods!) of the class. In the case of the String class, there is only 1 attribute that matches these criteria, a static attribute called CASE_INSENTIVE_ORDER of the type Comparator<String>. We won’t go into more detail about what exactly this is for now, but please find out for yourself! It has something to do with sorting…

String field summary

If we scroll down a bit more, we find the following useful information at “Constructor summary”.

Constructor summary

The heading “Constructor summary” contains, as the name might suggest, a description of all possible constructors that the class String knows. There are probably many more constructors than you would have thought, but this is mainly because the String class is frequently used within Java when it comes to reading data.

String constructor summary

Note that there are some constructor calls where the description includes “Deprecated “. Basically this means that the specific constructor call is considered obsolete and will be removed in a later version of Java. (So if you want your program to be easily updatable in the future, do not use these constructors!)

Method summary

Below the list of constructors is the “Method summary”, possibly the most useful list of the entire page. In this list you will find all the public and protected methods offered by the class, including return types and any parameters. For ease of browsing, some filters are also included, these are:

String method summary

Note especially that on this page you can thus actually see the entire definition of a method. You now know literally everything about a method, except what exceptions it (can) throw. This is described in the detailed description of a method, which is at the bottom of the page. You can easily get to this by clicking on the name of a method.

Method details

Once you have clicked on a method, you will see the details of this method. First, you’ll see the official method signature, followed by a description. In this description you can see how the method should be used, what the individual parameters represent, what the method returns, and (possibly) by which interface / (abstract) superclass the method is enforced. In the case of the method charAt, the method must be included based on the implementation of the interface CharSequence. (If String didn’t implement this method, it cannot be a CharSequence.)

You will also see any exceptions that may be thrown including the reason(s) why they are thrown. So if you ever come across this exception, you can easily find out what (supposedly) went wrong here. (Note that here no distinction between checked and unchecked exceptions. All possible exceptions are listed).

String method details

With this information, you can now read the official javadoc! The only question you can ask yourself now is which class you want to use at what time. And this is actually where the slogan for programming anno 2024 comes in:

Everyone uses Google! (or StackOverflow, or ChatGPT?)

Because at the time of writing there are over 5000 classes to be found in the Java library, it is almost impossible to find “the right class” directly. In all programming courses so far we have used classes that 99% of all programmers also use (String, LocalDate, ArrayList, etc.), but most likely there are other classes out there that probably can do what you want. If you can imagine it, it is proably also already built! So it is mainly a matter of being to able to search properly and understand how you can use any given class based on their documentation. And that’s exactly what you’ll practice this week (too)!

At some point you will know most of the classes you using every day by heart, but until then: learn to read the documentation carefully instead of trying to memorize literal code examples.