Transcripted Summary

An exception is an unexpected event that occurs at runtime due to an error. Exceptions disrupt the normal flow of a program.

If we had this snippet of code, it would compile.



There would be no errors, there would be no warnings.

However, when we run this, we run into an error when the loop attempts to access index 3 of this array because there is no index 3.

So, we receive an ArrayIndexOutOfBoundsException and the program would crash.


# Handling Exceptions

You can handle exceptions within your code so that the program doesn't crash and so that you can provide a meaningful message in case of an error.

To do so, you use a try/catch clause.

Let's look at an example.



I'm going to write a program that creates a new file and we're going to handle exceptions that may occur.

So here I have an ExceptionHandling class.

Let's go ahead and make a call to a new method called createNewFile(), and we'll make that method in the class.

package chapter13;

public class ExceptionHandling {

    public static void main(String args[]){
        createNewFile();
    }

    public static void createNewFile(){

    }
}

We’re going to use the File class in Java to create a new object.

We give it the path of the file

public static void createNewFile(){
    File file = new File("resources/nonexistent.txt");
}

The File class has a method called createNewFile, so it can create this file if the “resources” directory exists, which it does not.

public static void createNewFile(){

    File file = new File("resources/nonexistent.txt");
    file.createNewFile();
}

Notice here, it's not compiling.



If we hover over here, we see that it says, “Unhandled exception”, and it gives us the exception name that is unhandled — IOException.

In order to handle this exception, we can place this statement in a try/catch block.

We type the word try and we follow that with curly braces, then we place the statement inside of the try block.

public static void createNewFile(){

    File file = new File("resources/nonexistent.txt");

    try{
        file.createNewFile();
    }
}

We follow the try block with a catch block. Notice that we have a compilation error because we don't have the catch block.

Inside of catch's parenthesis, we have to specify the exception that we're going to catch — createNewFile has the potential of throwing an IOException, so we want to catch what it's throwing.

So, we specify that, and we give this a name.

try{
    file.createNewFile();
}
catch (IOException e){

}

In most programs people just called this “e” or “ex”, something really short.

After the catch, we also put a set of curly braces, and this is a block of code.

So, what happens is Java is going to attempt to run this createNewFile. If an exception happens to be thrown, then we have provided a catch block. The code will come inside of here and execute everything in here.

We can maybe provide a print statement for our users.

catch (IOException e){
    System.out.println("Directory does not exist.");
}

Additionally, when an exception is caught, that exception has a stack trace as well, which will provide information about the error and also the path that the code took before getting there.

So, you can print this out as well. If we do e, which is the name of the exception, dot (.), we see this printStackTrace().



There's also a getMessage() that will show just the error message without the stack trace.

So, let's just choose the printStackTrace().

package chapter13;

public class ExceptionHandling {

    public static void main(String args[]){
        createNewFile();
    }

    public static void createNewFile(){
        File file = new File("resources/nonexistent.txt");
        try{
            file.createNewFile();
        }catch (Exception e){
            System.out.println("Directory does not exist.");
            e.printStackTrace();
        }
    }
}

And we can run this.



Notice our message was printed, “Directory does not exist.”, and then here is the stack trace. So, it tells us the path that it took, started from the main method, went to our createNewFile method, then went to the File.createNewFile method, and so on until it reached the exception.


# Polymorphism with Exceptions

When handling exceptions, you can also use the superclass as a way to catch broader exceptions.

If we look at the Javadoc for the ArrayIndexOutOfBoundsException, we see that it inherits from a chain of other exceptions with a top level exception class being Exception.



All exceptions inherit from the Exception class.

Let's take a look at our example.



Here we're catching the IOException because we know that that's what this is going to throw. However, let's say that this method threw more than one exception, or we weren't quite sure exactly what exception it was going to throw.

Then we can use polymorphism and instead of catching the IOException, we can specify a superclass.

Knowing that Exception is the top-most superclass for all exceptions, then we can place that here.

try{
    file.createNewFile();
}catch (Exception e){
    System.out.println("Directory does not exist.");
    e.printStackTrace();
}

It doesn't have to be just Exception, we can use any of the parents of that IOException.


# Handling Multiple Exceptions

There can be multiple catch clauses to handle different types of exceptions.

If the multiple catch clauses contain related exceptions, the subclass’ catch clause must appear first. Otherwise, it will never have the possibility of reaching other catch clauses.

Let's look at an example.

We're going to write a program that reads decimal numbers from a file. And then we're going to handle two types of exceptions that come from that program — the FileNotFoundException and InputMismatchException.

Let's comment out this one and we're going to make a call to a new one.

We'll say numbersExceptionHandling, and we'll create this method.

public class ExceptionHandling {

    public static void main(String args[]){
        // createNewFile();
        numbersExceptionHandling();
    }

    public static void numbersExceptionHandling(){
    }
}

Now in this method I'm going to create a new file object and then I want to read from that file.

I'm going to use the Scanner object, and instead of System.in, we're going to use the file.

public static void numbersExceptionHandling(){
    File file = new File("resources/numbers.txt");
    Scanner fileReader = new Scanner(file);
}

So, we're no longer reading from the console, we're going to read from the file instead.



If we hover over here, we see we have an unhandled exception, the FileNotFoundException.

So, let's place this in a try.

try{
    Scanner fileReader = new Scanner(file);
}catch(FileNotFoundException e){

}

Next, we want to read every line from the file, so we're going to use a while loop. And we saw this in the last chapter with iterators — read the number and then print that number to the console.

try{
    fileReader = new Scanner(file);

    while(fileReader.hasNext()){
        double num = fileReader.nextDouble();
        System.out.println(num);
    }
}catch(FileNotFoundException e){

}

Technically, we're okay. We weren’t required to put this nextDouble() inside of a try block, however there is a possibility that when we're reading from the file, if one of the lines in the file is not an actual double then we would get an exception, an InputMismatchException.

We can handle the case of the FileNotFoundException and we can also add another catch block for the InputMismatchException.

try{
    Scanner fileReader = new Scanner(file);

    while(fileReader.hasNext()){
        double num = fileReader.nextDouble();
        System.out.println(num);
    }
}catch(FileNotFoundException e){

}catch(InputMismatchException e){

}

So now, if either one of these exceptions is thrown it will go into the respective clause.

We haven't placed anything inside of these clauses yet. Let's say that all we wanted to do was print the stack trace, then we could add that in both clauses.



Let's say we also wanted to catch any other exception that occurs and so we add a 3rd catch block.



Notice now we have a compilation error because Exception is a superclass to FileNotFoundException and also to InputMismatchException. So, if any Exception is thrown, it's going to first check this one. It's going to catch this one. There's no way that it will ever get to the other two. So, it's unreachable.

Now, we can still include this one, but it would have to be at the end of these — which means if there is a FileNotFoundException, it will go there, if there's an InputMismatch, it will go there, and then finally if there's any other Exception it will come here.

I'm going to get rid of this one. If we look at the two that we have, they're fine.

This is okay. It works. However, there's some redundancy here. All we are doing is printing the stack trace. So, it's kind of foolish to have both of these listed this way.

We can simplify this by adding all of the exceptions that we want to catch into one catch block.

The way we do that is by using the pipe symbol: |.

So, if we have FileNotFoundException, we can use pipe and then we can specify any other exception we want.

catch(FileNotFoundException | InputMismatchException e){
    e.printStackTrace();
}

You can add however many more exceptions you want, just separate them with this pipe symbol.


public class ExceptionHandling {

    public static void main(String args[]){
        numbersExceptionHandling();
    }

    public static void numbersExceptionHandling(){
        File file = new File("resources/numbers.txt");
        try{
            Scanner fileReader = new Scanner(file);

            while(fileReader.hasNext()){
                double num = fileReader.nextDouble();
                System.out.println(num);
            }
        }catch(FileNotFoundException | InputMismatchException e){
            e.printStackTrace();
        }
    }
}

So, once we've done that, this will catch either one of these exceptions.


# The finally Clause

A finally clause can optionally be added below any catch clauses.

This clause is executed after try and after any catch clauses, even if the catch clauses don't execute.

Let's revisit the previous example and add a finally clause to close the file.

catch(FileNotFoundException | InputMismatchException e){
    e.printStackTrace();
}finally{
    fileReader.close();
}

After all of your catches, you can type in the word finally. This one does not have a parenthesis, but it does have the curly braces. With this finally block you put whatever else that you want to do.

Exceptions interrupt the flow of the program. Let's say for example, we have the exception and it was thrown right here when we tried to instantiate Scanner.

Then it would come into this catch block and would not execute the rest of this try block. So, adding the scanner.close() statement inside of the try block is not a good idea because if this exception is thrown, we'll come to catch block and this will never get executed.

The Finally Block

The finally block says, "Hey, even if you finish everything in the try, even if no exceptions are thrown, I will execute whatever you place inside of me." So, this will execute no matter what — if exceptions are thrown, or if exceptions are not thrown — finally will execute.

So, anything that you just need to get done you, would place inside of the finally block.

Now, we have an error because this is out of scope — remember, this fileReader is defined inside of only the try block.



We can fix this by declaring the fileReader outside of the try block. So, let's just do that and we'll initialize it inside the try block and then the finally block will close it.

public static void numbersExceptionHandling(){
    File file = new File("resources/numbers.txt");
    Scanner fileReader = null;
    try{
        fileReader = new Scanner(file);

        while(fileReader.hasNext()){
            double num = fileReader.nextDouble();
            System.out.println(num);
        }
    }catch(FileNotFoundException | InputMismatchException e){
        e.printStackTrace();
    }finally{
        fileReader.close();
    }
}

# Try with Resources

That’s just one way to handle this. We could also handle this without using a finally block.

There is an option that’s referred to as try with resources, which will allow you to add a parenthesis after the word try and inside of the parentheses, you can initialize the resource there.

For example, the Scanner object is a resource, so we can define it within these parenthesis, and get rid of the other places where we declared and initialized it.

File file = new File("resources/numbers.txt");

try(Scanner fileReader = new Scanner(file)){

    while(fileReader.hasNext()){
        double num = fileReader.nextDouble();
        System.out.println(num);
    }
}

Once we have the resource declared at this level, we no longer need the finally clause to close the resource. Try with resources allows you to specify a resource and Java will automatically close this resource on your behalf once done with the try/catch.

This only works with classes that implement the Closable or AutoClosable interfaces, and Scanner happens to be one of those.

So that’s an option when you’re working with resources and you were only going to use finally to close the resource.

Otherwise, you may have other cases where finally is a good approach to execute some other statements, and that’s fine as well.

So you have multiple options.



Resources



© 2024 Applitools. All rights reserved. Terms and Conditions Privacy Policy GDPR