Java reflection is a powerful feature that enables inspection and manipulation of classes, methods, fields, and constructors at runtime—even when their names are not known at compile time. This capability allows programs to examine or "reflect upon" themselves, and manipulate intenral properties of classes.
Consider this analogy: imagine you're handed a sealed device with no manual. Normally, you couldn't use it without knowing its functions. But if you had a tool that could reveal every button, switch, and internal setting—and even let you tweak them—you'd gain full control. That tool is what reflection provides in Java.
Core Capabilities of Reflection
- Introspection: Retrieve metadata about a class—its name, declared fields, methods, constructors, annotations, and generic type information.
- Dynamic Instantiation: Create objects without using the
newkeyword, useful when the class name is determined at runtime. - Access Control Bypass: Access and modify private members by disabling access checks via
setAccessible(true).
Practical Use Cases
- Frameworks like Spring and Hibernate use reflection for dependency inejction and object-relational mapping.
- Testing libraries (e.g., JUnit) dynamically invoke test methods.
- Serialization/deserialization tools inspect object structure to convert between formats (e.g., JSON ↔ Java objects).
Hands-On Example
First, define a simple class with private fields and methods:
public class User {
private String fullName;
private int userAge;
public User() {
this.fullName = "Anonymous";
this.userAge = -1;
}
public User(String fullName, int userAge) {
this.fullName = fullName;
this.userAge = userAge;
}
public void printDetails() {
System.out.println("User: " + fullName + ", Age: " + userAge);
}
private void updateProfile(String fullName, int userAge) {
this.fullName = fullName;
this.userAge = userAge;
}
}
Now, use reflection to interact with this class dynamically:
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class RuntimeInspector {
public static void main(String[] args) {
try {
// Load class dynamically
Class<?> userClass = Class.forName("User");
// Instantiate via default constructor
Constructor<?> ctor = userClass.getDeclaredConstructor();
Object userInstance = ctor.newInstance();
// Invoke public method
Method printMethod = userClass.getMethod("printDetails");
printMethod.invoke(userInstance);
// Modify private fields
Field nameField = userClass.getDeclaredField("fullName");
nameField.setAccessible(true);
nameField.set(userInstance, "Taylor Swift");
Field ageField = userClass.getDeclaredField("userAge");
ageField.setAccessible(true);
ageField.setInt(userInstance, 34);
printMethod.invoke(userInstance);
// Call private method
Method updateMethod = userClass.getDeclaredMethod(
"updateProfile", String.class, int.class
);
updateMethod.setAccessible(true);
updateMethod.invoke(userInstance, "Beyoncé", 42);
printMethod.invoke(userInstance);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
Key Considerations
- Performance: Reflective operations are slower than direct calls due to runtime resolution and security checks.
- Security: Bypassing access modifiers can violate encapsulation and introduce vulnerabilities if misused.
- Maintainability: Code using reflection is harder to debug and refactor since IDEs and compilers can’t fully analyze dynamic calls.