Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Core Java Concepts: Exception Handling, Reflection, Annotations, and Generics

Tech 1

Exception Handling

Overview of Exceptions

The Throwable class serves as the root of the exception hierarchy, extending Object. It branches into two main categories: Error and Exception.

Error represents severe system-level issues that are typically beyond the control of the programmer, often arising from the Java Virtual Machine (JVM) or environment. These include problems like OutOfMemoryError, NoClassDefFoundError, and StackOverflowError. Such errors usually indicate conditions from which recovery is not feasible within the application, and terminating the program may be the only recourse.

Exception denotes conditions that an application might anticipate and handle. These are often caused by logical flaws in the code and should be managed to allow the program to continue running where possible. Common examples include:

  • ArrayIndexOutOfBoundsException: Acccessing an array with an invalid index.
  • ClassCastException: Attmepting an invalid type cast.
  • IllegalArgumentException: Passing an illegal argument to a method.
  • NullPointerException: Dereferencing a null reference.
  • NoSuchElementException: Requesting a non-existent element from a collection.

Catching Exceptions

Use a try-catch block to encapsulate code that may throw exceptions and define handlers for specific exception types.

try {
    int[] values = {10, 20};
    System.out.println(values[5]); // Potential ArrayIndexOutOfBoundsException
} catch (ArrayIndexOutOfBoundsException ex) {
    ex.printStackTrace();
    System.out.println("Array index is out of bounds.");
}

Multiple catch blocks can be used to handle different exception types specifically. The Exception class can serve as a general catch-all, but it should be placed last to alow more specific exceptions to be caught first.

try {
    // Code that may throw various exceptions
} catch (ArrayIndexOutOfBoundsException ex) {
    // Handle array index issue
} catch (NullPointerException ex) {
    // Handle null reference
} catch (Exception ex) {
    // General exception handler
}

The finally block ensures execution of cleanup code regardless of whether an exception occurs.

try {
    // Risky operations
} catch (Exception ex) {
    // Handle exception
} finally {
    System.out.println("Cleanup executed.");
}

Throwing Exceptions

Exceptions can be thrown explicitly using the throw keyword.

// Instantiate and throw an exception
IllegalArgumentException iae = new IllegalArgumentException("Invalid input provided.");
throw iae;

// Or throw directly
throw new NumberFormatException("Cannot parse null string.");

Custom Exceptions

Create custom exceptions by extending an appropriate base class, typically RuntimeException.

public class ValidationException extends RuntimeException {
    public ValidationException(String message) {
        super(message);
    }
    
    public ValidationException(String message, Throwable cause) {
        super(message, cause);
    }
}

Assertions

Assertions are used during development and testing to validate assumptions. They are disabled by default in production.

public class AssertExample {
    static int threshold = 100;
    public static void main(String[] args) {
        assert threshold > 50 : "Threshold must be greater than 50";
        System.out.println("Assertion passed.");
    }
}

Logging

Modern Java applications use logging frameworks like JDK Logging, Log4j 2, SLF4J, and Logback for structured log output.

JDK Logging Example:

import java.util.logging.Logger;
import java.util.logging.Level;

public class AppLogger {
    private static final Logger LOG = Logger.getLogger(AppLogger.class.getName());
    public static void main(String[] args) {
        LOG.info("Application started.");
        LOG.log(Level.WARNING, "Potential issue detected.");
    }
}

Log4j 2 Configuration (log4j2.xml snippet):

<Configuration>
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %logger{36} - %msg%n"/>
        </Console>
    </Appenders>
    <Loggers>
        <Root level="info">
            <AppenderRef ref="Console"/>
        </Root>
    </Loggers>
</Configuration>

SLF4J with Logback Example:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Service {
    private static final Logger LOG = LoggerFactory.getLogger(Service.class);
    public void process(int id, String name) {
        LOG.debug("Processing item with ID: {} and Name: {}", id, name);
        // Business logic
        LOG.info("Item processed successfully.");
    }
}

Reflection

Concept

Reflection enables inspection and manipulation of classes, fields, methods, and constructors at runtime, allowing dynamic behavior.

The Class Object

Every class loaded in the JVM has a corresponding Class object that provides metadata. There are three primary ways to obtain a Class instance:

// 1. Using Class.forName()
Class<?> clazz1 = Class.forName("java.lang.String");

// 2. Using .class syntax
Class<?> clazz2 = String.class;

// 3. Using getClass() on an instance
String str = "example";
Class<?> clazz3 = str.getClass();

Inspecting Fields

Reflection allows access to field names, types, and values, including private fields (with access override).

import java.lang.reflect.Field;

class Employee {
    private String name;
    public int id;
}

public class ReflectionDemo {
    public static void main(String[] args) throws Exception {
        Employee emp = new Employee();
        Class<?> empClass = emp.getClass();
        
        // Access public field
        Field publicField = empClass.getField("id");
        System.out.println("Field name: " + publicField.getName());
        
        // Access private field
        Field privateField = empClass.getDeclaredField("name");
        privateField.setAccessible(true); // Override access control
        privateField.set(emp, "Alice");
        System.out.println("Employee name: " + privateField.get(emp));
    }
}

Invoking Methods

Methods can be discovered and invoked dynamically.

import java.lang.reflect.Method;

class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
    private void log(String message) {
        System.out.println("Log: " + message);
    }
}

public class MethodInvocationDemo {
    public static void main(String[] args) throws Exception {
        Calculator calc = new Calculator();
        Class<?> calcClass = calc.getClass();
        
        // Invoke public method
        Method addMethod = calcClass.getMethod("add", int.class, int.class);
        int result = (int) addMethod.invoke(calc, 5, 3);
        System.out.println("Addition result: " + result);
        
        // Invoke private method
        Method logMethod = calcClass.getDeclaredMethod("log", String.class);
        logMethod.setAccessible(true);
        logMethod.invoke(calc, "Private method called via reflection.");
    }
}

Dynamic Proxies

Dynamic proxies create wrapper objects at runtime to intercept method calls, often used for cross-cutting concerns like logging or security.

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

interface Greeter {
    void greet(String name);
}

class SimpleGreeter implements Greeter {
    public void greet(String name) {
        System.out.println("Hello, " + name);
    }
}

class LoggingHandler implements InvocationHandler {
    private Object target;
    public LoggingHandler(Object target) {
        this.target = target;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before method: " + method.getName());
        Object result = method.invoke(target, args);
        System.out.println("After method: " + method.getName());
        return result;
    }
}

public class ProxyDemo {
    public static void main(String[] args) {
        Greeter realGreeter = new SimpleGreeter();
        Greeter proxyGreeter = (Greeter) Proxy.newProxyInstance(
            Greeter.class.getClassLoader(),
            new Class[]{Greeter.class},
            new LoggingHandler(realGreeter)
        );
        proxyGreeter.greet("World");
    }
}

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.