We use the Reflection API to find out about the internals of a Java class — its fields, constructors, methods and annotations. We can also use it to programmatically instantiate objects, invoke methods and access fields at runtime.
This is a very powerful API. It is used extensively by JEE containers, the Spring framework, GUI builders and other APIs.
Heart of the Reflection API
The heart of the Reflection API is the java.lang.Class
class. It allows us to load classes into memory at runtime.
The java.lang.Class
class includes methods to load classes by name, get references to all or specific constructors, methods and fields, get the annotations on all these elements, and test whether an object is a primitive, an enum
, an array, etc.
Reflection API Classes
The classes Field
, Constructor
and Method
are in the java.lang.reflect
package. The Annotation
interface is in the java.lang.annotation
package.
Each class provides information about, and access to, that member. For example, the Method
class has an invoke()
method that can be used to invoke the method on a specific object. The Field
class has a variety of get()
and set()
methods that can read and write to the fields of a specific object. It’s possible to access restricted members in ways that aren’t directly allowed by the compiler.
Each of the previous classes (Class
, Field
, Constructor
and Method
) implement the AnnotatedElement
interface. This interface represents an annotated element running in the JVM and allows annotations to be read reflectively. This allows us to access any annotations with RUNTIME
retention.
Reflection API: Example 1
Here’s a simple application that programmatically inspects the contents of any class (exception handling is not shown):
import java.lang.reflect.*; public class ReflectionTester { public static void main (String args[]) { // Load a class using name from the command line. Class<?> cl = Class.forName(args[0]); System.out.println ("----- FIELDS -----"); Field fld[] = cl.getFields(); for (int i = 0; i < fld.length; i++) System.out.println (fld[i]); System.out.println ("----- CONSTRUCTORS -----"); Constructor con[] = cl.getConstructors(); for (int i = 0; i < con.length; i++) System.out.println (con[i]); System.out.println ("----- METHODS -----"); Method met[] = cl.getMethods(); for (int i = 0; i < met.length; i++) System.out.println (met[i]); } }
We would run the application from the console as follows:
java ReflectionTester SomeClassName
Reflection API: Example 2
But that’s not all, folks! The previous example just printed out the elements of the class. We can also instantiate objects by calling appropriate constructors, invoke their methods and modify fields (even if they are private!).
To create an object on the fly at runtime, the class Class
has a newInstance()
method which invokes the default no-argument constructor of that class. To call a specific constructor, the Constructor
class has a newInstance(Object ... initParameters)
method which invokes the appropriate constructor with the specified parameters.
Let’s assume we have a class with a constructor taking a String
parameter. We can instantiate an object of that class by calling that constructor and passing in a String
. The code below assumes that the String
value is also passed in at the command line as follows:
java ReflectionTester SomeClassName SomeString
We will then call the instantiated object’s toString()
and hashCode()
method (remember that all classes inherit those methods from the Object
class).
// Load a class using the name provided on the command line. Class<?> cl = Class.forName(args[0]); // Instantiate an object using the specified constructor Constructor<?> c = cl.getConstructor( new Class<?>[]{java.lang.String.class}); Object ob = c.newInstance(new Object[]{args[1]}); // Get a reference to the toString() method and invoke it // The ob parameter is essentially the "this" variable Method m = cl.getMethod("toString", new Class<?>[]{}); System.out.println(m.invoke(ob, new Object[]{})); // Get a reference to the hashCode() method and invoke it m = cl.getMethod("hashCode", new Class<?>[]{}); System.out.println(m.invoke(ob, new Object[]{}));
Conclusion
The Reflection API might look intimidating, but we don’t have to worry too much about its complexity. It’s used mostly by developers of containers.
But we do need to keep reflection in mind when we write classes. We must follow standard naming conventions, so that object properties can be automatically discovered by the container at runtime. (You can read my post on JavaBeans and POJOs, as well as my post on canonical classes.)
I’m always interested in your opinion, so please leave a comment. Your feedback helps me write tips that help you.