Java Reflection API
Java Reflection is an API that allows a program to inspect and modify its own runtime structure. Using the Reflection API, you can examine classes, interfaces, constructors, methods, and fields at runtime, even if you did not know their names at compile time.
Reflection is primarily located in the java.lang.reflect package.
The Class Object
At the center of reflection is the java.lang.Class class. For every loaded type (class, interface, array, primitive, or void), the JVM maintains an immutable Class object that contains metadata about it.
There are three main ways to obtain a Class object:
// 1. Using class literal syntax (Compile-time)
Class<String> clazz1 = String.class;
// 2. Using an object instance's getClass() method (Runtime)
String str = "Hello";
Class<? extends String> clazz2 = str.getClass();
// 3. Using the fully qualified class name string (Dynamic)
Class<?> clazz3 = Class.forName("java.lang.String");
Inspecting Class Members
Once you have a Class object, you can query its constructors, fields, and methods.
| Member Type | Public Only (Includes Inherited) | All Declared (Excludes Inherited) |
|---|---|---|
| Fields | getFields() | getDeclaredFields() |
| Methods | getMethods() | getDeclaredMethods() |
| Constructors | getConstructors() | getDeclaredConstructors() |
[!NOTE] Methods prefixed with
getDeclaredwill return all members (public, protected, default, and private) declared directly by the class, but will not include inherited members.
Bypassing Encapsulation
One of the most powerful (and controversial) features of reflection is the ability to bypass Java's access control checks. By calling setAccessible(true) on a field, method, or constructor, you can read or write private fields and invoke private methods.
Field privateField = MyClass.class.getDeclaredField("secretKey");
privateField.setAccessible(true); // Bypass private modifier
privateField.set(myInstance, "newSecretValue");
Practical Code Example
The following example demonstrates how to use reflection to inspect a class, instantiate it dynamically, read and modify private fields, and invoke a private method.
Step 1: The Target Class
public class SecretAgent {
private String codeName;
private int clearanceLevel;
private SecretAgent(String codeName, int clearanceLevel) {
this.codeName = codeName;
this.clearanceLevel = clearanceLevel;
}
private void performSecretMission(String location) {
System.out.println("Agent " + codeName + " (Clearance " + clearanceLevel +
") is executing mission in: " + location);
}
}
Step 2: The Reflection Inspector
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectionDemo {
public static void main(String[] args) {
try {
// 1. Get Class object
Class<?> agentClass = Class.forName("SecretAgent");
// 2. Instantiate using the private constructor
// Get private constructor: SecretAgent(String, int)
Constructor<?> constructor = agentClass.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true); // Bypass private constructor check
Object agent = constructor.newInstance("007", 5);
System.out.println("Successfully instantiated SecretAgent using reflection.");
// 3. Inspect and modify private fields
Field codeNameField = agentClass.getDeclaredField("codeName");
codeNameField.setAccessible(true);
// Read value
String currentName = (String) codeNameField.get(agent);
System.out.println("Original codeName: " + currentName);
// Modify value
codeNameField.set(agent, "Double-O-Seven");
System.out.println("Updated codeName: " + codeNameField.get(agent));
// 4. Invoke a private method
// Get private method: performSecretMission(String)
Method missionMethod = agentClass.getDeclaredMethod("performSecretMission", String.class);
missionMethod.setAccessible(true);
// Invoke method: agent.performSecretMission("London")
missionMethod.invoke(agent, "London");
} catch (Exception e) {
e.printStackTrace();
}
}
}
Pros and Cons of Reflection
Advantages
- Extensibility: Allows frameworks (like Spring, Hibernate, Jackson, JUnit) to load, configure, and inspect user-defined classes dynamically.
- Universal Tools: Enables development tools like debuggers, IDE auto-completion engines, and code visualizers to inspect class structures dynamically.
Disadvantages
- Performance Overhead: Dynamic resolution involves searching metadata, which prevents compiler and JIT optimizations. Reflection operations are significantly slower than direct Java code execution.
- Security Risks: Accessing private details breaks encapsulation, which can lead to unexpected side effects and violates security constraints under the Java Security Manager.
- Loss of Type Safety: Resolving types as strings (e.g.
Class.forName("Name")) bypasses compile-time type checks, leading to runtime failures if class names are misspelled.