Skip to content
muhammad.khair edited this page Nov 30, 2020 · 4 revisions

A "hopefully" Exceptional post on Exceptions

When it comes to creating large scale projects, it is likely to experience numerous issues with one’s code; and there can be issues where it can be used in contexts that the designer has not intended and potentially damaging the structure and operations. Thus, this is where exceptions can come in and help in both troubleshooting and protecting one’s code.

There are various forms of exceptions one can utilise/experience. It should be noted that an exception is a subclass of Throwable. There are 2 main groups under Exception; RuntimeException and others. RunTimeException happens during run-time (as the name suggests) where there are scenarios where one’s input/variable assigns creates some form of conflict where the operation cannot perform its task. One common example would be the ArithmeticException (which extends RuntimeException), where one divides some Number by zero.

When it comes to other exceptions, one can define their own exceptions (by extending from the desired Exception class) or utilise those provided in Java API (over here) and modify its message to provide a more meaningful message for others to decipher.

Throw vs Throws

To put it simply, throws is used in a method signature to signal to entities that call it on what the method can potentially cause, while throw is the action performed in the method that generates the exception and “throw” it. The throws also ensure that follow up methods would either have to deal with the exception or thrown again for others to deal with.

class MyOwnException extends Exception { }

public static DataBase generate(int seed) throws MyOwnException {
    if (seed < 0) {
       throw new MyOwnException("invalid seed!");
    }
    return new DataBase(seed);
}

Basic Applications

Try-Catch Blocks

Try-catch blocks are a basic means to handle exceptions, acting as a layered barrier for different kinds of Exceptions that can be experienced. There is also the try-catch-finally block that has an additional portion that will execute where the block is successfully computed, or an exception is caught and handled.

// consider we have these exceptions
// here you can see that MyOtherException <: MyOwnException <: Exception 
class MyOwnException extends Exception { }
class MyOtherException extends MyOwnException { }

public void verify(List<Data> database) {
    try {
        this.scan(database.getParameters()); // logic of code here
        // this method can call either of the three exceptions mentioned earlier

    } catch(MyOtherException ex) {
        // backup plan C

    } catch(MyOwnException ex) {
        // backup plan B

    } catch(Exception ex) {
        // backup plan A
    
    } finally {
        // clear evidence
    }
}

Do take note that the form of exception checking to the different catch statements goes in a linear-topdown fashion. Thus if a general or superclass Exception is to be caught at the top, it would not carry on to a more specific Exception catching below. This is also why it is a standard fashion where the most specific form of Exceptions are placed at the top of the catch block, and the bottom the most general types.

// consider we have these exceptions
// here you can see that MyOtherException <: MyOwnException <: Exception 
class MyOwnException extends Exception { }
class MyOtherException extends MyOwnException { }

// now consider the 2 methods and notice the ordering of exception catching!

public void performSecret() {
    try {
        this.execute(); 
        // this method can call either of the three exceptions mentioned earlier

    } catch(Exception ex) {
        // NOTE THAT ALL EXCEPTIONS WILL BE CAUGHT HERE AND 
        // NOT EVEN CHECK AND EXECUTE OTHER BLOCKS

        // backup plan A

    } catch(MyOwnException  ex) {
        // backup plan B

    } catch(MyOtherException ex) {
        // backup plan C
    }
}

public void performSneaky() {
    try {
        this.execute(); 
        // this method can call either of the three exceptions mentioned earlier

    } catch(MyOtherException ex) {
        // backup plan C (more specific exception caught first)

    } catch(MyOwnException ex) {
        // backup plan B

    } catch(Exception ex) {
        // backup plan A (most general exception caught last)
    }
}

Specialised Defined Classes

Besides that, there could be containers that can hold such exceptions to prevent the whole code from breaking down. Two such examples would be the Sandbox<T> (see here) and Optional<T> (see here). In brief, a Sandbox<T> would be fed a value and would be assigned tasks to execute such as map(Function<? super T, ? extends U> mapper) and flatMap(Function<? super T, ? extends Sandbox<? Extends U>> mapper). But if an exception arises from such computations, it will not alert the user immediately, instead, it would hold the exception and its message, and only reveals itself once the user calls or checks on it. This allows a more convenient means to trace exceptions. Optional<T> is not entirely an exception handler, but one should note its importance in handling null, which is a key player in error and exception generations. See here for more about SandBox<T> and here for more about Optional<T>.

Parallel Exception Handling

When computing asynchronously, there are also means to handle exceptions. Utilising CompletedFuture<T>’s handle, whenComplete, and exceptionally methods (which are from the interface CompletionStage<T>), it is also able to deal with exceptions that occur in a parallel fashion.

CompletableFuture<Event> execute(double time) {
    return CompletableFuture
        .supplyAsync(() -> new ArriveEvent(time))
        .thenApplyAsync(x -> x.execute())
        .exceptionally(ex -> {
            System.out.println("Something's wrong here: " + ex.message());
            return new LeaveEvent(time);
        });
}

Assertion

Besides dealing with exceptions head-on, one can also call upon assert. In layman's terms, it basically acts as a means to ensure all the parameters and arguments are defined well enough for the following computation in a method. It is normally placed at the start of the method to ensure all is well, and sometimes also at the end to ensure it has done the job in a safe manner for future computations by other methods. If the parameters fail the set conditions under an assertion statement, an AssertionError will be thrown. Assertions are disabled when running a java .class file by default. To enable it, run the java .class file as such: java -ea <CLASSNAME>

public void payMeal(List<Food> consumed) {

    assert (!consumed.isEmpty()) : "You have not eaten anything here!";
    // first half is the conditions needed to be asserted,
    // the second (which is optional) the message if the assertion fails

    ...
    // payment and print of receipt
    ...

}
Clone this wiki locally