Reflection for Java Beginners

This tutorial outlines how to use reflection in Java for complete beginners.

Overview & Aims

In this tutorial, we will cover:

  • What reflection is
  • How to use reflection to bypass access modifiers

Requirements & Expected Knowledge

In order to follow this tutorial, I’ll assume that:

  • You know how to write basic code in Java
  • You know about Java’s access modifiers (private and public are sufficient)
  • You know about Java’s static keyword and how it affects methods and fields

Glossary

  • Invoke a method - Run a method
  • Field - A global variable declared inside a class (not one declared inside a method)

Reflection: An Introduction To Fields

Reflection basically allows you to write code in a particular language that can manipulate and analyse code written in the same language at runtime. In the context of Java, this means that we can write code in Java to modify how code runs in Java, whilst it is running. This allows a plethora of features that could never be accomplished without reflection, such as reading annotations that are attached to methods and running code that are not normally accessible.

Most tutorials would probably tell you why reflection is very bad practice and that it should almost never be used under any circumstances, but this tutorial won’t inform you of the cons of reflection - that’s for you to discover.

Accessing private fields

Reflection can be used to access private fields from external classes that you would not normally have access to. In the example below, you are able to retrieve the String object from a different class ExternalClass.java (You can view this file by pressing the file icon on the left).

Things to note from the above example:

  • The field we’re accessing is static.
  • Two exceptions are thrown: NoSuchFieldException and IllegalAccessException
  • Everything looks super confusing

Line by line analysis:

Field field = ExternalClass.class.getDeclaredField("myString");

This creates a new Field object which represents a Java field. It is accessed by getting the ExternalClass Class object, and running the getDeclaredField method, with the name of the variable as the input parameter.

If you are using an IDE, you may notice that there is a similar method getField. The difference here is that getDeclaredField will return private and protected fields, whereas getField will only return public fields. This method also throws a NoSuchFieldException (which in this example, is just rethrown), and it should be obvious when this exception is thrown.

field.setAccessible(true);

This simply makes the field accessible. In other words, it temporarily makes the field variable public.

field.get(null);

This retrieves the value of the field. Because the field is static, it is important to note that we pass null to the method, because we do not have an instance of the class (We will cover this later in this tutorial)

What is important to note about the get() method, is that it returns an Object. Since our field is actually a String, in order to use it as a String, you can simply cast it to a String using (String) field.get(null).

Everything else about private fields

There are 4 main types of field access:

  • Getting the value of a static field (which we’ve just done)
  • Setting the value of a static field
  • Getting the value of a non-static field
  • Setting the value of a non-static field

Getting non-static fields

Say you have a class ExampleClass, with a constructor that initialises a private field:

public class ExampleClass {

    private String myField;

    // Constructor
    public ExampleClass() {
        myField = "hello";
    }
	
    public String getMyField() {
        return this.myField;	
    }
}

We also have our reflection code that prints the result of the field:

import java.lang.reflect.Field;

public class Main {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        ExampleClass myClass = new ExampleClass();
		
        Field field = ExampleClass.class.getDeclaredMethod("myField");
        field.setAccessible(true);
        String result = (String) field.get(myClass);
		
        System.out.println(result);
    }
}

The main points to note are:

ExampleClass myClass = new ExampleClass();

Our class is instantiated. In other words, we have an instance of this object. This is necessary to access non-static fields.

String result = (String) field.get(myClass);

Using the casting method stated above, along with our instance of our class, we’re able to retrieve the result of a private field.

There are three main ways of getting hold of the Class object for a class:
  • Using the .class operator: ExampleClass.class
  • Using an instance of a class:
    ExampleClass myClass = new ExampleClass();
     myClass.getClass()
  • Using the Class.forName() method: Class.forName("ExampleClass")
For optimal readability and to ensure your reflection is less likely to produce unexpected results, the first option is preferred.

Setting private fields

Setting a field is as simple as getting the field.

import java.lang.reflect.Field;

public class Main {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IllegalArgumentException {
        ExampleClass myClass = new ExampleClass();
		
        Field field = ExampleClass.class.getDeclaredMethod("myField");
        field.setAccessible(true);
        field.set(myClass, "replacement");
		
        System.out.println(myClass.getMyField());
    }
}

The main line of code to note in this example is:

field.set(myClass, "replacement");

Here, we give the first argument the instance of the class, and the second argument as the new object to replace it with. Because we’re giving a field an argument and the compiler doesn’t know that you’re supplying the field with a suitable type (For example, you could try to set the field to an int), therefore it throws an additional IllegalArgumentException

To set values for a static field, an instance of the class is not required, therefore the following is sufficient:

field.set(null, "replacement");
For static fields, use field.set(null, VALUE)
For non-static fields, use field.set(INSTANCE, VALUE), where INSTANCE is your local instance of the class

Reflection: An Introduction To Methods

Reflection allows you to access private methods from other classes and invoke them whenever you want. The technique for invoking methods is pretty much the same as if you were doing reflection for fields:

public class ExampleClass {
    private static void myMethod() {
        System.out.println("hello!");
    }
	
    private void myNonStaticMethod() {
        System.out.println("I'm not static!");
    }
}
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;

public class Main {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {		
        Method method = ExampleClass.class.getDeclaredMethod("myMethod");
        method.setAccessible(true);
        method.invoke(null);
    }
}

In this case, we are using the method.invoke() method to execute the method that we have gained access to. Just like the fields, because it is a static method, we don’t need to include an instance and null is passed as the parameter.

Using the knowledge of how reflection works with fields, it should come naturally that you can provide an instance to invoke non-static methods:

ExampleClass myClass = new ExampleClass();
		
Method method = myClass.getClass().getDeclaredMethod("myNonStaticMethod");
method.setAccessible(true);
method.invoke(myClass);

Methods With Parameters

Methods on their own are pretty simple. But what if we want to use reflection on a method that has parameters?

Take the following example:

public class ExampleClass {
    
    private static String myMethod(String myString, int myInt, Object myObj) {
        System.out.println("Input String: " + myString);
        System.out.println("Input Int: " + myInt);
        System.out.println("Input Object: " + myObj);
        return "hello";
    }
    
}

Line by line analysis:

Method method = ExampleClass.class.getDeclaredMethod("myMethod", String.class, int.class, Object.class);

Here, we are getting a declared method called myMethod. In the ExampleClass.java, myMethod takes in 3 parameters: a String, an int and an Object. We basically tell Java that the method we’re looking for is called “myMethod” and has the three parameter types of String, int and Object. This is done by providing the classes for those parameters.

String result = (String) method.invoke(null, "string", 2, new Point(2, 3));

This bit of code has a lot going on. Firstly, the method.invoke() statement is used to run the code as normal. The first parameter is null, because we’re invoking a static method. The following three parameters are the arguments that we want to pass to the method.

Also note that in the myMethod method, it returns a String. The return value is accessed by casting the result of invoke() to a String. This result is assigned to the variable result in the code above.

Summary

Reflection is rather simple once you’ve gotten the hang of it. There are a few things to keep in mind:

  • If you are using a static field/method, use null as the first parameter. Otherwise, you need to supply an instance of the class which has that method.
  • Reflection throws a tonne of exceptions. The common ones are outlined below:

    Exception What it means
    NoSuchMethodException The method name could not be found
    NoSuchFieldException The field name could not be found
    IllegalAccessException Thrown when using .setAccessible()
    IllegalArgumentException Thrown when setting the value of a field or invoking a method
    InvocationTargetException Thrown when invoking a method

Appendix A: Constructors

Constructors in reflection are almost identical to using methods via reflection. Instead of using .invoke, you use .newInstance():

import java.lang.reflect.Constructor;

// Code here

public void someMethod() throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException {
    Constructor constructor = ExampleClass.class.getDeclaredConstructor();
    ExampleClass myClass = (ExampleClass) constructor.newInstance();
}

In addition, this throws:

  • NoSuchMethodException for when it cannot find the specific constructor
  • InstantiationException if it fails to instantiate the class

Private constructors can be accessed by using constructor.setAccessible(true) as you would expect

Appendix B: Caching Reflection

Because reflection is incredibly performance heavy (due to the overhead created by using reflection), it is often a good idea to cache reflection so access to the same methods/fields are quicker. Caching normally consists of creating a Map between class names to Class<?> objects, as well as method/field names to Method or Field objects respectively.

Written on February 21, 2019