Reflection in Java is a mechanism that allows a program to inspect and modify its own structure and behavior at runtime. It provides access to class metadata (e.g., methods, fields, constructors, annotations) and enables dynamic operations like invoking methods, instantiating objects, or modifying fields—even if they are private. Here’s why it’s useful, with practical examples:
Why Reflection is Useful
- Dynamic Class/Method Loading (e.g., plugins, frameworks).
- Accessing Private/Protected Members (e.g., testing, debugging).
- Building Generic Tools (e.g., serializers, ORM libraries).
- Runtime Adaptability (e.g., working with unknown classes).
- Annotation Processing (e.g., frameworks like Spring).
Practical Examples
1. Instantiating a Class by Name (Dynamic Loading)
Load and create an object of a class whose name is provided at runtime (e.g., from a config file):
String className = "com.example.MyClass"; // Read from config
Class<?> clazz = Class.forName(className);
Object instance = clazz.getDeclaredConstructor().newInstance();
2. Invoking a Method Dynamically
Call a method by its name (e.g., for scripting or command execution):
class Calculator {
public int add(int a, int b) { return a + b; }
}
Calculator calc = new Calculator();
Method method = Calculator.class.getMethod("add", int.class, int.class);
int result = (int) method.invoke(calc, 5, 3); // result = 8
3. Modifying Private Fields
Change the value of a private field (e.g., testing legacy code):
class SecretVault {
private String password = "1234";
}
SecretVault vault = new SecretVault();
Field field = SecretVault.class.getDeclaredField("password");
field.setAccessible(true); // Bypass "private" access
field.set(vault, "newPassword");
System.out.println(vault.password); // "newPassword" (if accessible)
4. Reading Annotations at Runtime
Process custom annotations (e.g., for validation or logging):
@Retention(RetentionPolicy.RUNTIME)
@interface Author {
String name();
}
@Author(name = "Alice")
class MyBook { }
// Read the annotation
Class<?> clazz = MyBook.class;
Author author = clazz.getAnnotation(Author.class);
System.out.println(author.name()); // "Alice"
5. Dynamic Proxy for Logging
Create a proxy to log method calls (Aspect-Oriented Programming):
interface Database { void connect(); }
class RealDatabase implements Database {
public void connect() { System.out.println("Connected"); }
}
// Create a logging proxy
Database proxy = (Database) Proxy.newProxyInstance(
Database.class.getClassLoader(),
new Class[]{Database.class},
(obj, method, args) -> {
System.out.println("Log: Method " + method.getName() + " called");
return method.invoke(new RealDatabase(), args);
}
);
proxy.connect();
// Output:
// Log: Method connect called
// Connected
6. Copying Object Properties
Copy fields from one object to another (e.g., for cloning or mapping):
class User {
String name;
int age;
}
User source = new User();
source.name = "Alice";
source.age = 30;
User target = new User();
for (Field field : User.class.getDeclaredFields()) {
field.setAccessible(true);
Object value = field.get(source);
field.set(target, value);
}
System.out.println(target.name); // "Alice"
7. Enum Exploration
List all constants of an enum dynamically:
enum Color { RED, GREEN, BLUE }
Class<?> enumClass = Color.class;
Object[] enumConstants = enumClass.getEnumConstants();
for (Object constant : enumConstants) {
System.out.println(constant); // RED, GREEN, BLUE
}
8. Dynamic Array Creation
Create an array of a type determined at runtime:
Class<?> componentType = String.class;
Object stringArray = Array.newInstance(componentType, 3);
Array.set(stringArray, 0, "Hello");
Array.set(stringArray, 1, "World");
// stringArray → ["Hello", "World", null]
When to Use Reflection
Use Case | Example |
---|---|
Frameworks | Spring (dependency injection), Hibernate (ORM), JUnit (test runners). |
Serialization | Jackson/Gson (JSON), JAXB (XML). |
Plugins/Extensions | Loading classes from external JARs. |
Debugging Tools | Inspecting object state at runtime. |
When to Avoid Reflection
- Performance-Critical Code: Reflection is slower than direct access.
- Security-Sensitive Code: Bypasses encapsulation (e.g.,
private
fields). - Simple Applications: Prefer direct code for readability.
Key Takeaways
- Pros:
- Enables frameworks, dynamic behavior, and runtime adaptability.
- Critical for annotation processing and generic utilities.
- Cons:
- Performance Overhead: Slower due to runtime checks.
- Complexity: Harder to debug and maintain.
- Security Risks: Can violate encapsulation.
Reflection is a double-edged sword—use it when necessary (e.g., building libraries or tools) but avoid it in everyday application code.