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
andpublic
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
andIllegalAccessException
- Everything looks super confusing
Line by line analysis:
1
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.
1
field.setAccessible(true);
This simply makes the field accessible. In other words, it temporarily makes the field
variable public
.
1
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
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:
1
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.
1
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.
- 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")
Setting private fields
Setting a field is as simple as getting the field.
1
2
3
4
5
6
7
8
9
10
11
12
13
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:
1
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:
1
field.set(null, "replacement");
field.set(null, VALUE)
For non-static fields, use
field.set(INSTANCE, VALUE)
, where INSTANCE
is your local instance of the classReflection: 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:
1
2
3
4
5
6
7
8
9
public class ExampleClass {
private static void myMethod() {
System.out.println("hello!");
}
private void myNonStaticMethod() {
System.out.println("I'm not static!");
}
}
1
2
3
4
5
6
7
8
9
10
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:
1
2
3
4
5
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:
1
2
3
4
5
6
7
8
9
10
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:
1
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.
1
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()
:
1
2
3
4
5
6
7
8
9
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 constructorInstantiationException
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.