Core Java

How to use Reflection effectively

This article is part of our Academy Course titled Advanced Java.

This course is designed to help you make the most effective use of Java. It discusses advanced topics, including object creation, concurrency, serialization, reflection and many more. It will guide you through your journey to Java mastery! Check it out here!

1. Introduction

In this part of the tutorial we are going to briefly look through a very interesting subject called reflection. Reflection is the ability of the program to examine or introspect itself at runtime. Reflection is an extremely useful and powerful feature which significantly expands the capabilities of the program to perform its own inspections, modifications or transformations during its execution, without a single line of code change. Not every programming language implementation has this feature supported, but luckily Java has adopted it since its beginning.

2. Reflection API

The Reflection API, which is part of the Java standard library, provides a way to explore intrinsic class details at runtime, dynamically create new class instances (without the explicit usage of the new operator), dynamically invoke methods, introspect annotations (annotations have been covered in part 5 of the tutorial, How and when to use Enums and Annotations), and much, much more. It gives the Java developers a freedom to write the code which could adapt, verify, execute and even modify itself while it is being running.

The Reflection API is designed in quite intuitive way and is hosted under the java.lang.reflect package. Its structure follows closely the Java language concepts and has all the elements to represent classes (including generic versions), methods, fields (members), constructors, interfaces, parameters and annotations. The entry point for the Reflection API is the respective instance of the Class< ? > class. For example, the simplest way to list all public methods of the class String is by using the getMethods() method call:

final Method[] methods = String.class.getMethods();
for( final Method method: methods ) {
    System.out.println( method.getName() );
}

Following the same principle, we can list all public fields of the class String is by using the getFields() method call, for example:

final Field[] fields = String.class.getFields();
for( final Field field: fields ) {
    System.out.println( field.getName() );
}

Continuing the experimentation with the String class using reflection, let us try to create a new instance and call the length() method on it, all that by using the reflection API only.

final Constructor< String > constructor = String.class.getConstructor( String.class );
final String str = constructor.newInstance( "sample string" );
final Method method = String.class.getMethod( "length" );
final int length = ( int )method.invoke( str );
// The length of the string is 13 characters

Probably the most demanded use cases for reflection revolve around annotation processing. Annotations by themselves (excluding the ones from Java standard library) do not have any effect on the code. However, Java applications can use reflection to inspect the annotations present on the different Java elements of their interest at runtime and apply some logic depending on annotation and its attributes. For example, let us take a look on the way to introspect if the specific annotation is present on a class definition:

@Retention( RetentionPolicy.RUNTIME )
@Target( ElementType.TYPE )
public @interface ExampleAnnotation {
    // Some attributes here
}

@ExampleAnnotation
public class ExampleClass {
    // Some getter and setters here
}

Using the reflection API, it could be easily done using the getAnnotation() method call. The returned non-null value indicates that annotation is present, for example:

final ExampleAnnotation annotation =
    ExampleClass.class.getAnnotation( ExampleAnnotation.class );

if( annotation != null ) {
    // Some implementation here
}

Most of the Java APIs nowadays are including annotations to facilitate their usage and integration for developers. The recent versions of the very popular Java specifications like Java API for RESTful Web Services (https://jcp.org/en/jsr/detail?id=339), Bean Validation (https://jcp.org/en/jsr/detail?id=349), Java Temporary Caching API (https://jcp.org/en/jsr/detail?id=107), Java Message Service (https://jcp.org/en/jsr/detail?id=343), Java Persistence (https://jcp.org/en/jsr/detail?id=338) and many, many more are built on top of annotations and their implementations usually heavily use the Reflection API to gather metadata about the application being run.

3. Accessing generic type parameters

Since the introduction of generics (generics are covered in part 4 of the tutorial, How and When To Use Generics), the Reflection API has been extended to support the introspection of generic types. The use case which often pops up in many different applications is to figure out the type of the generic parameters that particular class, method or other element has been declared with. Let us take a look on the example class declaration:

public class ParameterizedTypeExample {
    private List< String > strings;

    public List< String > getStrings() {
        return strings;
    }
}

Now, while inspecting the class using Reflection it would be very handy to know that the strings property is declared as generic type List with String type parameter. The code snippet below illustrates how it could be done:

final Type type = ParameterizedTypeExample.class
    .getDeclaredField( "strings" ).getGenericType();

if( type instanceof ParameterizedType ) {
    final ParameterizedType parameterizedType = ( ParameterizedType )type;
    for( final Type typeArgument: parameterizedType.getActualTypeArguments() ) {
        System.out.println( typeArgument );
    }
}

The following generic type parameter will be printed on the console:

class java.lang.String

4. Reflection API and visibility

In the part 1 of the tutorial, How to create and destroy objects, we have met first time the accessibility an visibility rules which are supported by the Java language. It may come up as a surprise, but the reflection API is able to modify, in a certain way, the visibility rules on a given class member.

Let us take a look on the following example of the class with a single private field name. The getter to this filed is provided, but the setter is not, and this is by intention.

public static class PrivateFields {
    private String name;

    public String getName() {
        return name;
    }
}

Obviously, it is apparent for any Java developer that the name field cannot be set using Java language syntax constructs as the class does not provide the way to do that. Reflection API on the rescue, let see how it could be done by changing field’s visibility and accessibility scope.

final PrivateFields instance = new PrivateFields();
final Field field = PrivateFields.class.getDeclaredField( "name" );
field.setAccessible( true );
field.set( instance, "sample name" );
System.out.println( instance.getName() );

The following output will be printed on the console:

sample name

Please notice that without the field.setAccessible( true ) call, the exception will be raised at runtime saying that the member of class with modifiers private cannot be accessed.

This feature of the reflection API is often used by test scaffolding or dependency injection frameworks in order to get access to the intrinsic (or not exposable) implementation details. Please, try to avoid using it in your applications unless you really have no other choice.


 

5. Reflection API pitfalls

Also, be aware that even though the reflection API is very powerful, it has a few pitfalls. First of all, it is a subject of security permissions and may not be available in all environments your code is running on. Secondly, it could have a performance impact on your applications. From execution prospective, the calls to reflection API are quite expensive.

Lastly, reflection API does not provide enough type safety guarantees, forcing developers to use Object instances in most of the places, and is quite limited in transforming constructor / method arguments or method return values.

Since the Java 7 release, there are a couple of useful features which could provide much faster, alternative way to access some functionality used to be available only through reflection calls. The next section will introduce you to them.

6. Method Handles

The Java 7 release introduced a new very important feature into the JVM and Java standard library – method handles. Method handle is a typed, directly executable reference to an underlying method, constructor or field (or similar low-level operation) with optional transformations of arguments or return values. They are in many respects better alternative to method invocations performed using the Reflection API. Let us take a look on a code snippet which uses method handles to dynamically invoke the method length() on the String class.

final MethodHandles.Lookup lookup = MethodHandles.lookup();
final MethodType methodType = MethodType.methodType( int.class );
final MethodHandle methodHandle =
    lookup.findVirtual( String.class, "length", methodType );
final int length = ( int )methodHandle.invokeExact( "sample string" );
// The length of the string is 13 characters

The example above is not very complicated and just outlines the basic idea of what method handles are capable of. Please compare it with the same implementation which uses the Reflection API from Reflection API section. However it does look a bit more verbose but from the performance and type safety prospective method handles are better alternative.

Method handles are very powerful tool and they build a necessary foundation for effective implementation of dynamic (and scripting) languages on the JVM platform. In the part 12 of the tutorial, Dynamic languages support, we are going to look on couple of those languages.

7. Method Argument Names

The well-known issue which Java developers have been facing for years is the fact that method argument names are not preserved at runtime and were wiped out completely. Several community projects, like for example Paranamer (https://github.com/paul-hammant/paranamer), tried to solve this issue by injecting some additional metadata into the generated byte code. Luckily, Java 8 changed that by introducing new compiler argument –parameters which injects the exact method argument names into the byte code. Let us take a look on the following method:

public static void performAction( final String action, final Runnable callback ) {
	// Some implementation here
}

In the next step, let us use the Reflection API to inspect method argument names for this method and make sure they are preserved:

final Method method = MethodParameterNamesExample.class
    .getDeclaredMethod( "performAction", String.class, Runnable.class );
Arrays.stream( method.getParameters() )
    .forEach( p -> System.out.println( p.getName() ) );

With the -parameters compiler option specified, the following argument names will be printed on the console:

action
callback

This very long-awaited feature is really a great relief for developers of many Java libraries and frameworks. From now on, much more pieces of useful metadata could be extracted using just pure Java Reflection API without a need to introduce any additional workarounds (or hacks).

8. What’s next

In this part of the tutorial we have covered reflection API, which is the way to inspect your code, extract useful metadata out of it or even modify it. Despite all its drawbacks, reflection API is very widely used in most (if not all) Java applications these days. In next part of the tutorial we are going to talk about scripting and dynamic languages support in Java.

9. Download the Source Code

This was a lesson of Reflection, part 11 of Advanced Java course. You may download the source code here: advanced-java-part-11

Andrey Redko

Andriy is a well-grounded software developer with more then 12 years of practical experience using Java/EE, C#/.NET, C++, Groovy, Ruby, functional programming (Scala), databases (MySQL, PostgreSQL, Oracle) and NoSQL solutions (MongoDB, Redis).
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button