Demonstration with Code
Consider a simple entity representing a bank account holder.
package io.demo.model;
public class AccountHolder {
private String holderName;
private int holderAge;
private final int balance = 5000;
public String getHolderName() {
return holderName;
}
public void setHolderName(String holderName) {
this.holderName = holderName;
}
public int getHolderAge() {
return holderAge;
}
public void setHolderAge(int holderAge) {
this.holderAge = holderAge;
}
// Withdraw funds
public int withdrawFunds() {
System.out.println("Withdrew: " + balance);
return balance;
}
// Deposit single amount
public void deposit(int amount) {
System.out.println("Deposited: " + amount);
}
// Deposit with name verification
public void deposit(String name, int amount) {
System.out.println(name + " deposited: " + amount);
}
}
Below is the test class comparing standard object interaction versus reflection-based interaction.
package io.demo.test;
import java.lang.reflect.Method;
import io.demo.model.AccountHolder;
public class ReflectionDemo {
public static void main(String[] args) throws Exception {
// Load class dynamically
Class<?> clazz = Class.forName("io.demo.model.AccountHolder");
AccountHolder instance = (AccountHolder) clazz.getDeclaredConstructor().newInstance();
// Approach 1: Standard direct method call
System.out.println("Direct Access:");
int cash = instance.withdrawFunds();
System.out.println("Received: " + cash);
System.out.println("------------------------");
// Approach 2: Invoking methods via Reflection
System.out.println("Reflection Access (No Args):");
Method withdrawMethod = clazz.getMethod("withdrawFunds");
Object result = withdrawMethod.invoke(instance);
System.out.println("Received via Reflection: " + result);
System.out.println("------------------------");
// Single parameter method
System.out.println("Reflection Access (Single Param):");
Method depositSingle = clazz.getMethod("deposit", int.class);
depositSingle.invoke(instance, 1500);
System.out.println("------------------------");
// Multiple parameter method
System.out.println("Reflection Access (Multiple Params):");
Method depositMulti = clazz.getMethod("deposit", String.class, int.class);
depositMulti.invoke(instance, "Alex", 2500);
}
}
The Concept of Reflection in Object-Oriented Programming
Frameworks are essentially pre-built software structures that allow developers to build applications on top of them, significantly reducing boilerplate code.
Reflection is the mechanism that allows a program to inspect and modify its own structure (classes, methods, fields) at runtime. In Java, this means treating class components as objects.
The driving force behind Java's reflection capability was the need to manipulate classes using string identifiers. While languages like JavaScript allow dynamic object creation natively, Java introduced reflection to maintain type safety while still allowing dynamic loading and execution. This enables the runtime creation of objects based on configuration rather than hard-coded logic.
Mechanics of Java Reflection
There are three primary ways to obtain a Class object:
- Source Code Phase:
Class.forName("fully.qualified.ClassName"). This loads the class into memory. It is commonly used in scenarios where class names are read from configuration files. - Class Object Phase:
ClassName.class. This is often used for type passing in method arguments. - Runtime Phase:
instance.getClass(). Defined in the rootObjectclass, this is useful when you already have an object and need its metadata.
Note that the same .class file is loaded only once per JVM runtime, so all three methods return the same referenec for a specific class.
Core Capabilities of the Class Object
The Class object provides methods to dissect the structure of a class:
- Fields (Member Variables):
Field[] getFields(): Retrieves all public fields.Field getField(String name): Gets a specific public field.Field[] getDeclaredFields(): Retrieves all fields regardless of modifiers.
- Constructors:
Constructor<?>[] getConstructors()Constructor<T> getConstructor(Class<?>... types)
- Methods:
Method[] getMethods()Method getMethod(String name, Class<?>... types)
Working with Reflected Objects
Field: Used to read and write object attributes.
// field.set(obj, value);
// Object val = field.get(obj);
// field.setAccessible(true); // Bypasses private access checks (Violent Reflection)
Constructor: Used to instantiate objects.
// T instance = constructor.newInstance(args...);
Method: Used to execute functions.
// Object returnValue = method.invoke(obj, args...);
Reflection as the Foundation of Frameworks
A key requirement for a framework is the ability to create objects and invoke methods without changing the framework's source code. This is achieved through configuration.
Implementation Strategy:
- Define the target class (e.g.,
AccountHolder). - Create a properties file (e.g.,
config.properties) containing the fully qualified class name and the method to execute. - Build a framework class that:
- Reads the configuration file.
- Uses
Class.forName()to load the class. - Uses
newInstance()orConstructorto create the object. - Uses
Method.invoke()to run the desired logic.