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:
- See the building's blueprint without opening walls (class structure)
- Count rooms and identify their purpose (methods, fields)
- Check room dimensions and features (method parameters, return types)
- Even access locked rooms with a master key (private members!)
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
- Frameworks: Powers Spring, Hibernate, and most Java frameworks
- Flexibility: Build generic, reusable code
- Dynamic Behavior: Load classes at runtime, plugin systems
- Tools: IDE code completion, debuggers use reflection
- Understanding: Helps you understand how frameworks work
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.