Reflection API

Inspecting and Modifying Code at Runtime

← Back to Index

What is Reflection?

Imagine having X-ray vision to see inside a sealed box, and magic hands that can reach through and rearrange things inside. Reflection gives Java programs this superpower - the ability to inspect and modify classes, methods, and fields at runtime!

Real-World Analogy: Building Inspector

A building inspector can:

Reflection lets Java code be its own inspector!

Simple Example - The Magic

class Person {
    private String name = "Alice";
    private int age = 25;
}

// Normal Java - Can't access private fields!
Person p = new Person();
// System.out.println(p.name); // ❌ ERROR! name is private

// With Reflection - Break the rules!
Class<?> clazz = p.getClass();
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true);  // Bypass private!
String name = (String) nameField.get(p);
System.out.println(name);  // ✅ "Alice" - accessed private field!

Getting Class Information

Three Ways to Get Class Object

// Way 1: From an object instance
Person p = new Person();
Class<?> clazz1 = p.getClass();

// Way 2: From class literal
Class<?> clazz2 = Person.class;

// Way 3: From class name string (useful for dynamic loading)
Class<?> clazz3 = Class.forName("com.example.Person");

Inspecting Class Details

Class<?> clazz = Person.class;

// Get names
System.out.println(clazz.getName());         // "com.example.Person"
System.out.println(clazz.getSimpleName());  // "Person"

// Get package
System.out.println(clazz.getPackage().getName());

// Get parent class
Class<?> parent = clazz.getSuperclass();

// Get interfaces
Class<?>[] interfaces = clazz.getInterfaces();

// Check modifiers
int mods = clazz.getModifiers();
System.out.println(Modifier.isPublic(mods));     // true/false
System.out.println(Modifier.isAbstract(mods));   // true/false

Working with Fields

class Person {
    public String name = "Alice";
    private int age = 25;
    protected String address = "NYC";
}

Class<?> clazz = Person.class;
Person p = new Person();

// Get ALL declared fields (public, private, protected)
Field[] allFields = clazz.getDeclaredFields();
for (Field f : allFields) {
    System.out.println(f.getName() + ": " + f.getType());
}

// Get only PUBLIC fields (including inherited)
Field[] publicFields = clazz.getFields();

// Get specific field
Field ageField = clazz.getDeclaredField("age");

// Read private field
ageField.setAccessible(true);  // REQUIRED for private!
int ageValue = (int) ageField.get(p);
System.out.println("Age: " + ageValue);  // 25

// Modify private field
ageField.set(p, 30);
System.out.println("New age: " + ageField.get(p));  // 30

Working with Methods

class Calculator {
    public int add(int a, int b) {
        return a + b;
    }

    private int multiply(int a, int b) {
        return a * b;
    }

    public void display(String msg) {
        System.out.println(msg);
    }
}

Class<?> clazz = Calculator.class;
Calculator calc = new Calculator();

// Get all methods
Method[] methods = clazz.getDeclaredMethods();
for (Method m : methods) {
    System.out.println(m.getName());
}

// Get specific public method (specify parameter types!)
Method addMethod = clazz.getMethod("add", int.class, int.class);

// Invoke method
Object result = addMethod.invoke(calc, 5, 3);
System.out.println("5 + 3 = " + result);  // 8

// Invoke private method
Method multiplyMethod = clazz.getDeclaredMethod("multiply", int.class, int.class);
multiplyMethod.setAccessible(true);  // Bypass private
result = multiplyMethod.invoke(calc, 5, 3);
System.out.println("5 * 3 = " + result);  // 15

// Method with no return (void)
Method displayMethod = clazz.getMethod("display", String.class);
displayMethod.invoke(calc, "Hello!");  // Prints "Hello!"

Creating Objects Dynamically

class Person {
    public Person() {
        System.out.println("No-arg constructor");
    }

    public Person(String name, int age) {
        System.out.println("Name: " + name + ", Age: " + age);
    }
}

// Normal way
Person p1 = new Person();

// Reflection way - no-arg constructor
Class<?> clazz = Person.class;
Object p2 = clazz.getDeclaredConstructor().newInstance();

// With constructor parameters
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
Object p3 = constructor.newInstance("Alice", 25);

// Load class by name (dynamic!)
String className = "com.example.Person";
Class<?> dynamicClass = Class.forName(className);
Object p4 = dynamicClass.getDeclaredConstructor().newInstance();

Real-World Use Cases

1. Dependency Injection (Spring Framework)

public class UserController {
    @Autowired  // Spring uses reflection!
    private UserService userService;
}

// Spring internally does:
// 1. Finds @Autowired fields via reflection
// 2. Creates UserService instance
// 3. Sets field value using reflection (setAccessible!)

2. JSON Serialization (Jackson, Gson)

// Convert Java object to JSON
Person p = new Person("Alice", 25);
String json = objectMapper.writeValueAsString(p);

// Jackson internally:
// 1. Uses reflection to find all fields
// 2. Reads field values via reflection
// 3. Converts to JSON format

// Convert JSON back to object
Person p2 = objectMapper.readValue(json, Person.class);

// Jackson:
// 1. Creates empty Person object via reflection
// 2. Sets field values from JSON using reflection

3. Testing Frameworks (JUnit, Mockito)

public class MyTest {
    @Test  // JUnit finds this via reflection
    public void testSomething() {
        // test code...
    }

    @BeforeEach  // Reflection identifies setup methods
    public void setup() {
        // setup code...
    }
}

// JUnit uses reflection to:
// 1. Find all @Test methods
// 2. Create test class instance
// 3. Invoke @BeforeEach methods
// 4. Invoke @Test methods
// 5. Invoke @AfterEach methods

4. ORM Frameworks (Hibernate, JPA)

@Entity
public class User {
    @Id
    private Long id;

    @Column
    private String name;
}

// Hibernate uses reflection to:
// 1. Find @Entity classes
// 2. Map fields to database columns
// 3. Create objects from database rows
// 4. Read/write field values

Performance Considerations

// Direct call - FAST
long start = System.nanoTime();
for (int i = 0; i < 1000000; i++) {
    calc.add(5, 3);
}
long end = System.nanoTime();
System.out.println("Direct: " + (end - start) + "ns");

// Reflection call - SLOW (10-50x slower)
Method method = clazz.getMethod("add", int.class, int.class);
start = System.nanoTime();
for (int i = 0; i < 1000000; i++) {
    method.invoke(calc, 5, 3);
}
end = System.nanoTime();
System.out.println("Reflection: " + (end - start) + "ns");

Optimization Tip: Cache Reflection Objects

// BAD: Look up method every time (SLOW!)
for (int i = 0; i < 1000; i++) {
    Method m = clazz.getMethod("add", int.class, int.class);
    m.invoke(calc, 5, 3);
}

// GOOD: Cache method lookup (FASTER!)
Method m = clazz.getMethod("add", int.class, int.class);
for (int i = 0; i < 1000; i++) {
    m.invoke(calc, 5, 3);
}

Best Practices & Warnings

Reflection Drawbacks
  • Performance: 10-50x slower than direct calls
  • Security: Breaks encapsulation (accesses private members)
  • Type Safety: Errors at runtime, not compile time
  • Code Clarity: Harder to read and understand
  • Refactoring: IDEs can't safely rename reflected members
When to Use Reflection

DO use for:

  • ✅ Building frameworks and libraries
  • ✅ Dependency injection containers
  • ✅ Object-Relational Mapping (ORM)
  • ✅ Serialization/deserialization
  • ✅ Testing frameworks and mocking
  • ✅ Plugin architectures

DON'T use for:

  • ❌ Regular application business logic
  • ❌ When you can use interfaces/polymorphism instead
  • ❌ Performance-critical code
  • ❌ Simple getter/setter access

Security Considerations

// Reflection can bypass security!
class Secrets {
    private static final String PASSWORD = "secret123";
}

// Attacker can access it via reflection!
Field f = Secrets.class.getDeclaredField("PASSWORD");
f.setAccessible(true);
String stolen = (String) f.get(null);

// Use SecurityManager to prevent this in production!

Why It Matters

Remember

Reflection is like a skeleton key - powerful but should be used responsibly! Use it when you need the flexibility, but prefer normal Java code when possible.