Reflection is a built-in mechanism to introspect a Java program at the time of execution. It can be used to inspect, modify, and transform a Java program without affecting existing code. This powerful feature can be used to expand the capabilities of a program, to examine the class or object internals at runtime. This article attempts to explore some of the intricacies and glimpses upon its effective use.
The Reflection API
The Reflection API is a part of the Standard Java API Library. It enables us to explore not only the intrinsic nature of the class but also instantiate one without using an explicit new operator at runtime. Using this API an executing code can ask an object for its class, explore its methods, the parameters it takes, return types, and constructors. This is done dynamically; the compiler has no knowledge of the classes that are loaded at runtime.
The API is hosted under the java.lang.reflect package. Structurally, the APIs are not different from the Java language and comprise all the language elements such as classes, methods, fields, constructors, interfaces, annotations, and so forth.
The class named Class<?> is the most important class in the Reflection API and the basis of introspection and reflection mechanism. It is a final class; hence, it cannot be extended.
public final class Class<T> extends Object implements Serializable, GenericDeclaration, Type, AnnotatedElement
Any class loaded into Java Runtime creates an instance of Class which represents the loaded class or an interface. As we know, the Object class is the parent of all the classes in Java; it offers a method called getClass(). We can obtain a reference of the instantiated Class object by using this method. Alternatively, the static forName() method defined in the Class also can be used to load an unknown class and get a reference of its Class object. The Class class provides a set of methods to get the class name, its methods, modifiers, constructors, and so on. For example, the getName() method of the Class can be used to obtain the full qualified name of the class it represents. Similarly, the getModifiers() method returns Java language modifiers; the getConstructors() method returns an array which contains Constructor objects reflecting all the public constructors of the class represented by this Class object. The getDeclaredFields() method returns an array of Field objects reflecting all the fields declared by the class or interface represented by this Class object, and so on.
For example, we easily can list all the public methods of the Object class as follows:
Method[] methods = Object.class.getMethods(); for(Method method:methods){ System.out.println(method.toString()); }
Similarly, we can retrieve field information of the Integer class.
Field[] fields = Integer.class.getFields(); for(Field field:fields){ System.out.println(field.toString()); }
The newInstance() method is used to instantiate an object that the current class Class represents. It throws IllegalAccessException if the class or no-argument constructor is inaccessible. It throws another exception, called InstantiationException, typically, if the Class represents an abstract class, interface, array class, primitive type, Void, or if it does not have a no-argument constructor either default or provided by the programmer.
try { Constructor<String> constructor = String.class.getConstructor(String.class); String strObj = constructor.newInstance("Hello"); Method method = String.class.getMethod("length"); int len = (int)method.invoke(strObj); System.out.println(len); }catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | InstantiationException ex){ ex.printStackTrace(); }
A Reflection on Annotation
A Java application can introspect on annotation by using Reflection APIs. These APIs help in inspecting annotation elements present in the code at runtime and modify them as per requirement. For example, we can use the API to discover and introspect a specific annotation declared within the class definition as follows:
package com.mano.examples; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public@interface DemoAnnotation { // ... } package com.mano.examples; @DemoAnnotation public class DemoClass { // ... }
We can find out if the annotation @DemoAnnotation is applied in the DemoClass or not in the following way.
DemoAnnotation annotation = DemoClass.class .getAnnotation(DemoAnnotation.class); if(annotation==null){ System.out.println("DemoAnnotation is not applied"); } else { System.out.println("DemoAnnotation is applied"); }
Because the use of annotation in Java is abundant, such as for Java API for RESTful Web Services, Java Persistent, JMS, Bean validation, and the like. We can efficiently use the Reflection API to obtain the information about annotations used in the running application.
Reflection on Generics
The Reflection API supports introspection on generics types as well. By using these APIs, we can discover the type of generic parameters that a class, or any other element, is designated with. Suppose there is a class as follows:
package com.mano.examples; public class Invoice implements Payable { private String itemNumber; private String itemName; private int quantity; private double unitPrice; public Invoice(String itemName, int quantity, double unitPrice) { // ... } public int getQuantity() { // ... return quantity; } public double getUntPrice() { // ... return unitPrice; } @Override public double calcAmount() { return getQuantity()*getUntPrice(); } } package com.mano.examples; import java.util.List; public class DemoClass { private List<Invoice> invoices; public List<Invoice> getInvoices(){ return invoices; } }
We can reflect upon the property of the Invoice class declared as the generic type List with the Invoice type parameter as follows.
try { Type type = DemoClass.class .getDeclaredField("invoices").getGenericType(); if(type instanceof ParameterizedType){ ParameterizedType parameterizedType = (ParameterizedType) type; for(Type t:parameterizedType.getActualTypeArguments()){ System.out.println(t); } } }catch (NoSuchFieldException ex){ ex.printStackTrace(); }
Modifying Application Code Using Reflection
With the Reflection API, we can modify code in a certain way. For example, let us declare a class with a private field and a public getter class as follows:
package com.mano.examples; public class SampleClass { private String greetings; public String getGreetings(){ return greetings; } }
Observe that the class has a getter method only and no setter method to set the string field greetings. This means that there is no way we can set any value of greetings at runtime. But, interestingly enough, we can take the Reflection API and change the visibility and accessibility scope of the field and set the field with a desired value as follows.
Try { SampleClass sampleClass = new SampleClass(); Field field = SampleClass.class .getDeclaredField("greetings"); field.setAccessible(true); field.set(sampleClass,"Hello, everybody. I am all set."); System.out.println(sampleClass.getGreetings()); }catch(NoSuchFieldException | IllegalAccessException ex){ ex.printStackTrace(); }
Note that the field.setAccessible(true) must be set; otherwise, we cannot access any class member declared as a private member. This feature is particularly helpful in implementing the dependency injection framework.
The SecurityException
Understand that both the getMethods() and the getConstructors() methods throw a SecurityException. This means that SecurityManager is present behind the scenes with a security policy to determine unsafe or sensitive operation. For example, when the checkMemberAccess(this, Member.PUBLIC) method is invoked, it denies access to methods and constructors within the class, indicating that method or constructor with public access cannot be invoked with dynamic discovery. The SecurityException also is thrown when the caller’s class loader is not found in the class hierarchy of the class loader of the current class. This essentially means that the loaded class is not part of the package that it belongs to. This raises a security breach and the package permission to the loaded class is denied.
Pitfalls of the Reflection API
No matter what, the Reflection APIs have security issues and may not be available in every programming environment where the application runs. It also can negatively impact performance of the application. The API is heavy and expensive in that sense. There is no guarantee of type safety. Programmers are forced to use Object instances in most cases. Also, it is quite limited in converting methods or constructor arguments and method return values.
Conclusion
This article just gave a glimpse of the reflection applicability in Java applications. Reflection is not a common phenomenon that can found in every programming language. Java supports this feature and has been part of it since version 1. Apart from many frameworks, there are many libraries which use Reflection APIs extensively. Perhaps the most common application we can find in any modern Java IDEs which proactively provides details of a class members as we type Java code. It is powerful and one of the most important features of Java.