Skip to Content
Course content

165: Inspecting Classes at Runtime

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've noticed a recurring theme when developers first encounter the Reflection API: they treat it like a read-only debugger. You might think that reflection is simply a way to "peek" at the metadata of a class you've already written—perhaps to print out a list of method names or check if a class implements a certain interface during a unit test. In other words, the misconception is that reflection is a passive observation tool for your own source code.

Reflection isn't just a mirror; it's a remote control

The truth is far more potent (and dangerous). Reflection isn't just for observing; it's for manipulating. You can instantiate objects of classes you didn't even know existed when you compiled your code, invoke methods by name, and—most controversially—bypass the private modifier entirely. I've used this in the past to fix bugs in third-party libraries where I couldn't change the source code but desperately needed to change a private internal state to make a feature work.

Consider this: if you have a java.lang.String, you know it has a private field called value (in older Java versions) or value (in newer ones). You can't access it in your code normally. But with reflection, you can reach inside that JDK class and pull the value out. It’s not just for your classes; it’s for every class loaded into the JVM.

The Class object as your entry point

Everything starts with the java.lang.Class object. Think of this as the "passport" for a type. You can get it three ways: using the class literal (String.class), calling getClass() on an instance, or using Class.forName("java.lang.String") if you only have the name as a string.


// Imagine we are building a plugin system where we don't know the class names at compile time
String className = "com.myapp.plugins.AdvancedCalculator"; 
Class> pluginClass = Class.forName(className);

// We can create an instance of this class dynamically
Object pluginInstance = pluginClass.getDeclaredConstructor().newInstance();

I'll be honest: Class.forName is where the real magic happens. It allows your program to be extensible. You can put a class name in a config file, and your app can load that logic without you ever having to recompile the main engine. This is exactly how Spring Framework handles Dependency Injection.

Breaking the visibility seal

Now, let's talk about the "dark arts." By default, if you try to call getDeclaredField("somePrivateField") and then call field.get(instance), Java will throw an IllegalAccessException. The JVM is trying to protect the encapsulation of the object. However, the Field and Method classes have a method called setAccessible(true).

When you call this, you're essentially telling the JVM, "I know what I'm doing, stop checking the access modifiers."


public class SecretVault {
    private String secretCode = "12345-ABC";
}

// ... inside your reflection logic ...
SecretVault vault = new SecretVault();
Field field = SecretVault.class.getDeclaredField("secretCode");
field.setAccessible(true); // This is the key move

String value = (String) field.get(vault); 
System.out.println("Stolen secret: " + value);

Use this sparingly. If you find yourself using setAccessible(true) in your everyday business logic, you're likely fighting the architecture of your system rather than working with it. But when you're building a generic JSON serializer or a testing framework, it's an indispensable tool.

The performance tax

One last thing I want you to keep in mind: reflection is slow. When you call a method normally, the JVM can optimize it, inline it, and make it lightning fast. When you use reflection, the JVM has to perform expensive lookups in the constant pool and verify access rights every single time. If you're doing this in a tight loop—say, inside a game render loop or a high-frequency trading system—you're going to tank your performance. Cache your Method and Field objects if you must use them repeatedly.




📋 Practical Task

Build a Dynamic Command Dispatcher

Your goal is to create a system that can execute methods of a class based on a string input, without using a giant switch statement. This mimics how many CLI tools and bot frameworks handle commands.

Requirements:

  • Create a class called CommandLibrary. Give it several public methods (e.g., sayHello(), printDate(), calculateSum(int a, int b)).
  • Create a class called CommandDispatcher with a method: public void execute(String methodName, Object... args).
  • Inside execute, use the Reflection API to:
    1. Find the method in CommandLibrary that matches the methodName string.
    2. Determine the parameter types of that method to ensure you can call getMethod()` correctly (Hint: you may need to iterate through getDeclaredMethods() to find the one with the matching name).
    3. Invoke that method on an instance of CommandLibrary using the provided args.
  • Handle potential exceptions like NoSuchMethodException or IllegalAccessException gracefully by printing a user-friendly error message.

Test Case: Your dispatcher should be able to take the string "calculateSum" and the integers 10, 20, and successfully trigger the calculateSum method in the library class to output 30.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.