Exceptions, Generics

Exceptions

Throwable

Alt text Throwable has two subclasses: Exception and Error.

Error class represents serious errors, which are generally not recommended to be caught by catch in our programs. We can only optimize Errors by optimizing the code. When an Error occurs, JVM generally chooses to terminate the thread.

Exception class has two subclasses: Checked Exception and Unchecked Exception. Checked Exception means that the exception will be checked at compile time. If the exception is not caught by catch or not throws, it will not pass compilation. This includes IOException, ClassNotFoundException. Unchecked Exception means that it will not be checked at compile time. It includes RuntimeException and its subclasses, such as NullPointerException, ArrayIndexOutOfBoundsException, and ClassCastException that may occur during forced type conversion.

try-catch-finally

finally is used to close input/output streams and database connections.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
public class FinallyDemo {
    public static void main(String[] args) {
        System.out.println(testReturn());
    }

    public static int testReturn() {
        int num = 10;
        try {
            num = 20;
            return num;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            num = 30;
            System.out.println("finally block executed, num value is: " + num);
        }
        return num;
    }
}

If return is executed inside the try block, the finally block will still be executed. The value of num is modified to 30, but the returned value is still 20. This is because the return value is already determined when return is executed inside the try block. If System.exit() is executed inside the try or catch block, the finally block will not be executed.

How to handle multiple exceptions?

  1. Multiple catch blocks catch different exceptions separately.
1
2
3
4
5
6
7
try {
    // Code that may throw exceptions
} catch (IOException e) {
    // Logic to handle IOException
} catch (SQLException e) {
    // Logic to handle SQLException
}
1
catch (IOException | SQLException e) {... }
  1. One catch block catches the parent class of these exceptions.

It is best to put the catch for subclass exceptions first, so that they are not uniformly treated as parent class exceptions. You can add some logging in the catch block to facilitate troubleshooting.

throw and throws

throw actively throws an exception.

1
throw new RuntimeException("Custom error message");

throws is placed in the function signature, declaring which exceptions this method may throw, allowing the caller to handle them. If the caller does not catch to handle it, it will be thrown layer by layer up the call stack until the main function.

1
public void readFile() throws IOException {... }

Generics

Generic Methods

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// <T> before void declares this as a generic method
public static <T> void swap(T[] array, int i, int j) {
    T temp = array[i];
    array[i] = array[j];
    array[j] = temp;
}

Integer[] intArray = {1, 2, 3};
swap(intArray, 0, 1);

String[] strings = {"apple", "banana", "cherry"};
swap(strings, 0, 2);

Generic Classes

A generic class can accept properties of various types.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class GenericClassExample {
    // Define static inner class
    static class Pair<K, V> {
        private K key;
        private V value;

        public Pair(K key, V value) {
            this.key = key;
            this.value = value;
        }

        public K getKey() {
            return key;
        }

        public V getValue() {
            return value;
        }
    }

    public static void main(String[] args) {
        // Usage
        Pair<String, Integer> pair = new Pair<>("age", 25);
        System.out.println("Key: " + pair.getKey() + ", Value: " + pair.getValue());

        Pair<String, String> pair2 = new Pair<>("name", "bob");
        System.out.println("Key: " + pair2.getKey() + ", Value: " + pair2.getValue());
    }
}

Generic Interfaces

When implementing this interface, specify a concrete class.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
interface DataAccessObject<T> {
    T getById(int id);
    void save(T entity);
}

// Implementation class
class UserDataAccessObject implements DataAccessObject<User> {
    @Override
    public User getById(int id) {
        // Logic to get user data from database
        return null;
    }
}

It is also possible not to specify a concrete class when implementing the interface, allowing the specific type to be specified when creating an instance of the GeneratorImpl class.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
interface Generator<T> {
    T method();
}

class GeneratorImpl<T> implements Generator<T> {
    @Override
    public T method() {
        return null;
    }
}

GeneratorImpl<String> stringGenerator = new GeneratorImpl<>();
String result = stringGenerator.method();

GeneratorImpl<Integer> integerGenerator = new GeneratorImpl<>();
Integer num = integerGenerator.method();

Bounded Types

1
class GenericClass<T extends Number> { }

Bytecode Files

  1. Type Erasure: At compile time, generic type parameters are erased, usually replaced by Object.
  2. Bridge Methods: Subclasses may override generic methods of superclasses. To ensure that the subclass method correctly overrides the superclass method, the compiler generates a bridge method.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class Parent<T> {
    public void set(T value) {
        System.out.println("Parent set: " + value);
    }
}

class Child extends Parent<String> {
    @Override
    public void set(String value) {
        System.out.println("Child set: " + value);
    }
}

public class Main {
    public static void main(String[] args) {
        Parent<String> parent = new Child();
        parent.set("Hello");
    }
}

Obviously, the output will be “Child set”. However, this actually calls the bridge method we cannot see in the subclass.

1
2
3
public void set(Object value) {
    set((String) value); // Calls the set(String value) method of the subclass
}
  1. Generic Signature: Generic information of generic classes, methods, and fields is stored in bytecode in a special format, which can be obtained through reflection APIs (e.g., getGenericType()).