placeholderReflection in Java

Reflection in Java

How Java reflection works, how to obtain Class objects, create instances, invoke methods, and read annotations at runtime, plus its performance trade-offs.

Reflection

Reflection lets a program inspect and modify its own structure and behavior at runtime. With reflection, a program can dynamically obtain detailed information about a class, including its fields, methods, and constructors, and invoke these members at runtime.

This capability gives Java some of the flavor of a dynamic language. Frameworks like Spring/Spring Boot and MyBatis, as well as Java annotations, rely heavily on reflection. Also, when you type a . after an object or class in an IDE, the IDE lists all available properties and methods of that object or class. That feature is powered by reflection as well.

The java.lang.reflect package provides the reflection API. With reflection, you can do the following at runtime:

  • Determine the class of any object
  • Construct an object of any class
  • Determine the fields and methods a class has
  • Obtain generic type information
  • Access the fields and invoke the methods of any object
  • Process annotations
  • Generate dynamic proxies

Core reflection APIs:

  • java.lang.Class: represents a class
  • java.lang.reflect.Method: represents a method of a class
  • java.lang.reflect.Field: represents a field of a class
  • java.lang.reflect.Constructor: represents a constructor of a class

Runtime Class

java.lang.Class<T> represents classes and interfaces in a running Java application.

When a Java program starts, the class loader loads the bytecode from .class files into memory.
During loading, the JVM creates an instance of java.lang.Class for each class in use to represent that class; this is commonly called the runtime class.

When does class initialization happen?

  • Active references to a class (always trigger initialization)
    • When the JVM starts, the class containing the main method is initialized first;
    • Creating an object of the class with new;
    • Accessing the class’s static members (except final constants) or static methods;
    • Making reflective calls on the class via the java.lang.reflect package;
    • When initializing a class, if its superclass has not been initialized, the superclass is initialized first;
  • Passive references to a class (do not trigger initialization)
    • When accessing a static field, only the class that actually declares the field is initialized;
    • Referencing a superclass’s static variable through a subclass does not initialize the subclass;
    • Defining an array of a class type does not trigger that class’s initialization;
    • Referencing a constant does not trigger initialization (constants are placed into the calling class’s constant pool during the linking phase)
// 4 ways to obtain a Class instancepublic void getClazz() throws ClassNotFoundException {    // Way 1: Employee.class    Class c1 = Employee.class;    // Way 2: emploee.getClass()    Employee p1 = new Employee();    Class c2 = p1.getClass();    // Way 3: Class.forName(String classPath); pass the fully qualified class name    Class c3 = Class.forName("reflection.Employee");    // Way 4: use a class loader: ClassLoader (just for reference)    ClassLoader classLoader = ReflectionTest.class.getClassLoader();    Class c4 = classLoader.loadClass("reflection.Employee");    System.out.println(c4); // class reflection.Employee    System.out.println(c1 == c4); // true}

Syntax

Getting a constructor via reflection and creating an instance

public class Person {    private String name;    private int age;    public Person(String name, int age) {        this.name = name;        this.age = age;    }}Class<?> personClass = Class.forName("Person");Constructor<?> constructor = personClass.getConstructor(String.class, int.class);Object personInstance0 = constructor.newInstance("Alice", 30);Object personInstance1 = constructor.newInstance(new Object[]{"Alice", 30});

Usage Example

package reflection;import java.lang.annotation.Annotation;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.lang.reflect.Parameter;public class Main {    public static void main(String[] args) throws ClassNotFoundException {        // Returns the Class object associated with the class or interface with the        // given string name.        Class<?> clazz = Class.forName("reflection.Employee");        printConstructors(clazz);        printMethods(clazz);        printClasses(clazz);        printAnnotations(clazz);    }    private static void printConstructors(Class<?> clazz) {        System.out.println("======== Constructor API ========");        Constructor<?>[] constructors = clazz.getDeclaredConstructors();        for (Constructor<?> constructor : constructors) {            System.out.println("Constructor Name: " + constructor.getName());            System.out.println("Constructor Parameter Count: " + constructor.getParameterCount());            Parameter[] parameters = constructor.getParameters();            for (Parameter parameter : parameters) {                System.out.println("Constructor's parameter: " + parameter);            }        }        // Create an object via reflection        System.out.println("New instance using reflection");        Constructor<?> cons;        try {            cons = clazz.getConstructor(int.class,double.class);            Employee employee = (Employee) cons.newInstance(1, 2.3);            System.out.println(employee);        } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {            e.printStackTrace();        }        System.out.println("======== Constructor API END ========\n");    }    private static void printMethods(Class<?> c) {        System.out.println("======== Method API ========");        Method[] methods = c.getDeclaredMethods();        System.out.println("Number of methods: " + methods.length);        for (Method method : methods) {            System.out.println("Method name: " + method.getName());            System.out.println("Method return type: " + method.getReturnType().getName());            System.out.println("Method parameter count: " + method.getParameterCount());            Parameter[] parameters = method.getParameters();            for (Parameter parameter : parameters) {                System.out.println("Method's Parameter: " + parameter);            }        }        // Invoke method        System.out.println("Invoke method using reflection");        try {            Method method = c.getMethod("getSalary");            System.out.println("Method return value: " + method.invoke(new Employee()));        } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException                | InvocationTargetException e) {            e.printStackTrace();        }        System.out.println("======== Method API END ========\n");    }    private static void printClasses(Class<?> c) {        System.out.println("======== Class API ========");        Class<?>[] classes = c.getDeclaredClasses();        for (Class<?> class1 : classes) {            System.out.println("Class: " + class1.getName());        }        System.out.println("======== Class API END ========\n");    }    private static void printAnnotations(Class<?> c) {        System.out.println("======== Annotation API ========");        // Get annotations on the class        Annotation[] classAnnotations = c.getDeclaredAnnotations();        for (Annotation annotation : classAnnotations) {            System.out.println("Class Annotation: " + annotation);        }        // Get annotations on methods        Method[] methods = c.getDeclaredMethods();        for (Method method : methods) {            Annotation[] methodAnnotations = method.getDeclaredAnnotations();            for (Annotation annotation : methodAnnotations) {                System.out.println("Method Annotation: " + annotation);            }        }        // Get annotations on constructors        Constructor<?>[] constructors = c.getDeclaredConstructors();        for (Constructor<?> constructor : constructors) {            Annotation[] constructorAnnotations = constructor.getDeclaredAnnotations();            for (Annotation annotation : constructorAnnotations) {                System.out.println("Constructor Annotation: " + annotation);            }        }        System.out.println("======== Annotation API END ========\n");    }}
package reflection;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)@interface MyClassAnnotation {    String value();}@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)@interface MyMethodAnnotation {    String value();}@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.CONSTRUCTOR)@interface MyConstructorAnnotation {    String value();}@MyClassAnnotation("This is a class annotation")public class Employee {    private int id;    private double salary;    public Employee() {    }    @MyConstructorAnnotation("This is a constructor annotation")    public Employee(int eid, double esal) {        this.id = eid;        this.salary = esal;    }    @MyMethodAnnotation("This is a method annotation")    public void show() {        System.out.println("Hello");    }    public int getId() {        return id;    }    public double getSalary() {        return salary;    }    @Override    public String toString() {        return "Employee [id=" + id + ", salary=" + salary + "]";    }    class Inner {        public void print() {            System.out.println("Inner class");        }    }}
$ java -versionopenjdk version "22.0.2" 2024-07-16OpenJDK Runtime Environment (build 22.0.2+9-70)OpenJDK 64-Bit Server VM (build 22.0.2+9-70, mixed mode, sharing)$ java ./Main.java======== Constructor API ========Constructor Name: reflection.EmployeeConstructor Parameter Count: 0Constructor Name: reflection.EmployeeConstructor Parameter Count: 2Constructor's parameter: int arg0Constructor's parameter: double arg1New instance using reflectionEmployee [id=1, salary=2.3]======== Constructor API END ================ Method API ========Number of methods: 4Method name: toStringMethod return type: java.lang.StringMethod parameter count: 0Method name: getIdMethod return type: intMethod parameter count: 0Method name: getSalaryMethod return type: doubleMethod parameter count: 0Method name: showMethod return type: voidMethod parameter count: 0Invoke method using reflectionMethod return value: 0.0======== Method API END ================ Class API ========Class: reflection.Employee$Inner======== Class API END ================ Annotation API ========Class Annotation: @reflection.MyClassAnnotation("This is a class annotation")Method Annotation: @reflection.MyMethodAnnotation("This is a method annotation")Constructor Annotation: @reflection.MyConstructorAnnotation("This is a constructor annotation")======== Annotation API END ========

Interview Notes

Pros and cons of reflection

Pros:

  • Obtain class instances dynamically at runtime, which improves flexibility;
  • Combines well with dynamic compilation

Cons:

  • Reflection is slow: it needs to parse bytecode and resolve objects in memory. Mitigations:
    • Call setAccessible(true) to skip the JDK security check and speed up reflective access;
    • When creating instances of a class repeatedly, caching helps a lot
    • Use ReflectASM, which speeds up reflection via bytecode generation
  • Relatively unsafe: it breaks encapsulation (reflection can access private methods and fields)