Lec 04 - Exception and Wrapper Classes
Slides:
Wrapper Class
In the previous lec, we have introduced how to write general code on reference type by using polymorphism. Then, how about the primitive type? How do we write general code for the primitive type (a.k.a make primitive types less primitive)? Here it comes — The Wrapper class.
A wrapper class is a class that encapsulates a type. For example, the wrapper class for int is called Integer, for double is called Double, etc. The table for the wrapper class for all primitive types in Java is summarised as follows:
byte
Byte
short
Short
int
Integer
long
Long
float
Float
double
Double
char
Character
boolean
Boolean
In particular, the wrapper class is considered as reference type and their instances can be created with new and stored on the heap.
Conversion between primitive types and its wrapper class
This is known as auto-boxing and unboxing.
Auto-boxing
This is used when you convert a primitive type to its wrapper class.
Here, the primitive value of int 4 is converted into an instance of Integer.
Unboxing
This is used when you convert an instance of a wrapper class to its primitive type.
Line 2 converts an instance of Integer back to int.
Performance
For the sake of general code and polymorphism, why don't we use wrapper class all the time? The reason is performance. Using primitive types is faster than using its wrapper class since the latter needs to instantiate an object every time it is being used.
Run-Time Class Mismatch
The main point of this part is about when to use the explicit casting?
Explicit casting is usually done during the narrowing type conversion process, this happens in compile-time! As the name suggests, the type must be "narrowed down". So,
for an explicit casting to be successful, the two types must have a subtype relationship.
Run-Time error or Compile-Time error?
When doing assignment, if the compile-time type of the two objects have no subtype relationship, then a compile-time error will be generated.
On the other hand, a run-time error will be generated when there is no subtype relationship between the run-time type of the two objects you are operating on.
Variance
Till now, we have seen the subtype relationship on classes and interefaces, this is trivial. However, how about the complex types such as arrays? Seems that the subtype relationship is not that trivial. So, here it comes — the principle of variance of types:
The variance of types refers to how the subtype relationship between complex types relates to the subtype relationship between components.
Variance of types
Let correspond to some complex type based on type , which means is the type of the "array" and is the type of each element/component in the "array". We say a complex type is:
covariant: which means if , then
contravariant: which means if , then
invariant: t is neither covariant nor contravariant
Exceptions
try-catch-finally block
try-catch-finally blockIn Java, we use try-catch-finally block in our process to handle exceptions. Their usage is summarised as follows:
Compile-error in catch block
catch blockIn the catch block, the first catch block that has an exception type compatible with the type of the thrown exception (i.e. a subtype) is selected to handle the exception. So, if there are blocks that are unreachable, a compile error will be generated! For example, the following code will generate a compile error (the second exception is the subclass of the first exception)
However, if we write the following code, it works fine,
Exceptions are always triggered at run-time.
For example, the following code won't generate a compile error, but will generate a runtime error!
Throwing Exceptions
We can control our program to throw an exception when our program doesn't behave as we expect. To throw an exception, we need to:
use the keyword
throw(notthrows)create a new exception object and throw it to the caller. (e.g.
IllegalArgumentException("radius cannot be negative."))
For example, the complete Java code should be as follows:
Checked vs. Unchecked Exceptions
An unchecked exception is an exception caused by a programmer's errors. They should not happen if perfect code is written. IllegalArgumentException, NullPointerException, ClassCastException are examples of unchecked exceptions. Generally, unchecked exceptions are not explicitly caught or thrown. They indicate that something is wrong with the program and cause run-time errors.
A checked exception is an exception that a programmer has no control over. Even if the code written is perfect, such an exception might still happen. The programmer should thus actively anticipate the exception and handle them. For instance, when we open a file, we should anticipate that in some cases, the file cannot be opened. FileNotFoundException and InputMismatchException are two examples of is an example of a checked exception. A checked exception must be either handled, or else the program will not compile.
Handle the exception
An unchecked exception, if not caught, will propagate automatically down the stack until either it is caught or if it is not caught at all, resulting in an error message displayed to the user.
A checked exception, however, must be handled. And this is done by either handling it in the calling method, or handling it in the caller. Below is an example regarding handling the FileNotFoundException)
Handle in the called method
Handle in the calling method
Line 2 is a method declaration which indicates that the method openFile() may throw a FileNotFoundException.
Control Flow of Exceptions
The use of Exceptions can affect the control flow of our program. For example, with the code following
Our normal control flow is as follows,

Then, what if we have thrown an exception E2 inside the m4()? (We decomment the Line 31 in the code above). Then, our control flow will become:

Good Practices for Exception Handling
Catch Exceptions to Clean Up
In the example, we may notice that if we have allocated some resources in m2() or m3(), they might not be deallocated because of the control flow of our exception handling.
So, it is recommended to handle the exception in the called method itself by using another try-catch-finally block. And if you still feel a need to pass the exception to the calling method, you can also do it by throwing this exception again in the catch block. For example,
Last updated