-
Notifications
You must be signed in to change notification settings - Fork 48
Exception Handling
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.
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);
}
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 Exception
s 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)
}
}
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>
.
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);
});
}
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
...
}
Peer Learning
Codecrunch Contributions
Piazza Contributions
Wiki Contributions
Guides
Setting Up Checkstyle
Setting Up Java
Setting Up MacVim
Setting Up Sunfire
Setting Up Unix For Mac
Setting Up Unix For Windows
Setting Up Vim
Setting up SSH Config
CS2030 Contents
Lecture 1 SummaryCompile-run vs Run-time Summary
Quick Guide To Abstraction
Generics and Variance of Types
Comparable vs Comparator
Summary of completable future
CS2030S Notes
ELI5 Optional.map vs Optional.flatMap
PECS Example Code
Java Collection Framework (Iterator)
Generic
Generic Type Parameter and Generic Wildcard
Calculator
Lambda-Expression
Single Abstract Method (SAM)
Method Reference
Functional Interfaces 2
Simple Usage of Sandbox
Associative-but-not-commutative
Higher Order function
Functional Programming
Calculator With Functor
Eager Evaluation VS Lazy Evaluation
Simple Usage of Lazy Evaluation
Lazy Evaluation for LazyList
Lazy Evaluation for BinaryTree
Stream
Parallel Stream
Optional
Simple Usage of Stream
Asynchronous Programming
Notes on CompletableFuture
Notes on CompletableFuture 2
Simple Usage of CompletableFuture
Mind Map
Exception Handling
Links
CS2030 Java Style Guide
CS2030 Javadoc Specification
JDK 11 Download Link
JDK 11 API Docs
Codecrunch
Piazza Forum