Java Reflection, part 2: fields, methods, constructors
Introduction
Reflection is the ability of an application to examine and modify its structure and behavior at runtime. The ability to?introspect structure?consists in the presence of the Core Reflection API for reading classes and their fields, methods, constructors, member classes and member interfaces. The ability to?modify behavior?consists in the presence of this API for getting and setting field values, invoking methods, and creating new instances using constructors.
The?Member?interface and its implementations - the?Field,?Method, and?Constructor?classes represent reflected fields and methods (which are members of classes and interfaces, according to the Java Language Specification) and constructors (which are not members).
This article is based on the Java 17 implementation in Oracle OpenJDK.
Discovery members and constructors
According to the Java Language Specification, class members include fields, methods, member classes and member interfaces. A class can declare members in its body or inherit them from its superclass and superinterfaces. Notice that constructors are not class members and therefore are not inherited.
The entry point for the Core Reflection API is the?Class?class. Among other methods, this class has the following methods for discovering fields, methods, constructors, member classes and member interfaces of the given type:
Methods grouped by one category, return either an array of all constructs (members and constructors) or a single construct by its name and/or parameter types.
Methods grouped by another category, return constructs by their accessibility and declaration. Methods that contain the fragment "declared" in their name, return all the declared constructs of the class or interface, including?public,?protected,?package access, and?private?constructs, but excluding inherited constructs. Methods that do not contain this fragment, return all the?public?constructs of the class or interface, including those declared by the class or interface and those inherited (except constructors) from superclass and superinterfaces.
Using members and constructors
The?Member?interface and its implementations have the following type hierarchy, which represents reflected fields, methods, and constructors.
Additionally, there is the?Parameter?class that represents reflected parameters of methods and constructors.
The Member interface
The?Member?interface is a superinterface for the?Field,?Method, and?Constructor?classes.
This interface declares the following methods:
The AccessibleObject class
The?AccessibleObject?class is an abstract superclass of the?Field,?Method, and?Constructor?classes that allows suppressing checks for Java language access control.
Java language access control is connected with two concepts of the Java Platform Module System: readability and accessibility.
Readability is the basis of?reliable configuration?and specifies that the caller module can be guaranteed to read types in the target module. Readability is established using the?requires?directive in the caller module.
Accessibility is the basis of?strong encapsulation?and specifies what packages (and?public?types in them) the target module exposes to the caller module. Accessibility is established using the?exports?directive in the target module.
Here are some methods that this class declares:
By default, Java language access control allows the use of:
Java language access control can be always suppressed by setting the?accessible?flag to?true?if the caller class and the target class are in the same module. If they are in different modules, the access control can be suppressed only if any of the following conditions are met:
Shallow reflection?is access without using the?accessible?flag. Only?public?members of?public?classes in exported packages can be accessed. The?exports?directive grants the package access at compile-time and for shallow reflection at runtime.
Deep reflection?is access with setting the?accessible?flag to?true. Any members of any classes in any package (whether exported or not) can be accessed. The?opens?directive grants the package access for deep reflection at runtime.
module caller.module {
requires target.module; // for readability
}
module target.module {
exports target.package to caller.module; // to access public members of public classes
opens target.package to caller.module; // to access any members of any classes
}
The Field class
The?Field?class represents a reflected static or an instance field. The class has methods for reading information about the field (name, modifiers, and type) and for getting and setting field values for a given object.
Here are some methods that this class declares:
(*) The?Field?class also declares similar methods for getting and setting values for?boolean,?byte,?char,?short,?int,?long,?float,?double?fields.
Code examples
The example class contains a field with a known name, modifiers, and a type.
class SomeClass {
public int someField;
}
The following code uses methods of the?Field?class for a?public?instance field. To access a?private?field, you should preliminarily execute the statement?field.setAccessible(true). To access a?static?field, you should use?null?instead of an instance object.
Object object = new SomeClass();
Class<?> clazz = object.getClass();
Field field = clazz.getDeclaredField("someField");
// methods inherited from the AccessibleObject class
assertTrue(field.canAccess(object));
// methods inherited from the Member class
assertEquals(SomeClass.class, field.getDeclaringClass());
assertEquals("someField", field.getName());
assertEquals("public", Modifier.toString(field.getModifiers()));
assertFalse(field.isSynthetic());
// methods declared in the Field class
assertEquals(int.class, field.getType());
assertEquals(0, field.getInt(object));
field.set(object, 1);
assertEquals(1, field.getInt(object));
The Executable class
The?Executable?class is an abstract superclass of the?Method?and?Constructor?classes that declares their shared functionality: parameter types and thrown exception types.
Here are some methods that this class declares:
The Method class
The?Method?class represents a reflected static or an instance method. The class has methods for reading information about the method (name, modifiers, parameter types, return type, and thrown exception types) and invoking the method for a given object.
Here are some methods that this class declares:
If a reflected method call via the?invoke?method throws an exception, it will be wrapped in an?InvocationTargetException.
Code examples
The example class contains a method with a known name, modifiers, a parameter, a return type, and a thrown exception.
class SomeClass {
public int someMethod(int someParameter) throws RuntimeException {
return someParameter;
}
}
The following code uses methods of the?Method?class for a?public?instance method. To access a?private?method, you should preliminarily execute the statement?method.setAccessible(true). To access a?static?method, you should use?null?instead of an instance object.
Object object = new SomeClass();
Class<?> clazz = object.getClass();
Method method = clazz.getDeclaredMethod("someMethod", int.class);
// methods inherited from the AccessibleObject class
assertTrue(method.canAccess(object));
// methods inherited from the Member class
assertEquals(SomeClass.class, method.getDeclaringClass());
assertEquals("someMethod", method.getName());
assertEquals("public", Modifier.toString(method.getModifiers()));
assertFalse(method.isSynthetic());
// methods inherited from the Executable class
assertEquals(1, method.getParameterCount());
assertArrayEquals(new Class[]{int.class}, method.getParameterTypes());
assertArrayEquals(new Class[]{RuntimeException.class}, method.getExceptionTypes());
assertFalse(method.isVarArgs());
// methods declared in the Method class
assertEquals(int.class, method.getReturnType());
assertFalse(method.isBridge());
assertFalse(method.isDefault());
assertEquals(1, method.invoke(object, 1));
The Constructor class
The?Constructor<T>?class (where?T?is the class in which the constructor is declared) represents a reflected constructor. The class has methods for reading information about the constructor (name, modifiers, parameter types, and thrown exception types) and creating new instances.
Here is a method that this class declares:
If a reflected constructor call via the?newInstance?method throws an exception, it will be wrapped in an?InvocationTargetException.
To create new instances, in addition to the?Constructor::newInstance?method, there is also the?Class::newInstance()?method. However, the latter has several limitations (can only invoke a no-argument constructor, can propagate?checked?exceptions thrown by the reflected constructor, can not bypass the access control) and has been deprecated since Java 9.
Code examples
The example class contains a constructor with a known name, modifiers, a parameter, and a thrown exception.
class SomeClass {
public SomeClass(int someParameter) throws RuntimeException {
}
}
The following code uses methods of the?Constructor?class on a?public?constructor. To access a?private?constructor, you should preliminarily execute the statement?constructor.setAccessible(true).
Class<SomeClass> clazz = SomeClass.class;
Constructor<SomeClass> constructor = clazz.getDeclaredConstructor(int.class);
// methods inherited from the AccessibleObject class
assertTrue(constructor.canAccess(null));
// methods inherited from the Member class
assertEquals(SomeClass.class, constructor.getDeclaringClass());
assertEquals("demo.SomeClass", constructor.getName());
assertEquals("public", Modifier.toString(constructor.getModifiers()));
assertFalse(constructor.isSynthetic());
// methods inherited from the Executable class
assertEquals(1, constructor.getParameterCount());
assertArrayEquals(new Class[]{int.class}, constructor.getParameterTypes());
assertArrayEquals(new Class[]{RuntimeException.class}, constructor.getExceptionTypes());
assertFalse(constructor.isVarArgs());
// methods declared in the Constructor class
SomeClass object = constructor.newInstance(1);
assertNotNull(object);
The Parameter class
The?Parameter?class represents a reflected parameter of a method or constructor. The class has methods to read information about the parameter (name, modifiers, and type).
Here are some methods that this class declares:
If the parameter name is present in the?class?file, then the?getName?method returns the actual parameter name. Otherwise, this method synthesizes the name in the form?argN, where?N?is the index of the parameter. Notice that?class?files do not store actual parameter names by default (mainly because larger?class?files can use more memory in the Java VM). To store parameter names in?class?files, the source Java files should be compiled with the?-parameters?option.
Code examples
The example class contains a constructor with one parameter with a known name, modifiers, and type.
class SomeClass {
private SomeClass(int someParameter) {
}
}
The following code uses methods of the?Parameter?class for the constructor parameter.
Class<?> clazz = SomeClass.class;
Constructor<?> constructor = clazz.getDeclaredConstructor(int.class);
Parameter[] parameters = constructor.getParameters();
assertEquals(1, parameters.length);
Parameter parameter = parameters[0];
// methods declared in the Parameter class
assertEquals(constructor, parameter.getDeclaringExecutable());
assertFalse(parameter.isNamePresent());
assertEquals("arg0", parameter.getName());
assertEquals("", Modifier.toString(parameter.getModifiers()));
assertEquals(int.class, parameter.getType());
Special classes
Sometimes reflection reveals constructs that are not explicitly declared in the source code. Some of these are?implicit?constructs whose presence is mandated by the Java Language Specification (for example, default no-argument constructors). Others are?synthetic?constructs that are neither explicitly nor implicitly declared in the source code but generated by the Java compiler (for example, bridge methods). This is because some features of the Java platform are implemented in the overlying Java language layer to make the underlying Java VM layer more simple and versatile. Examples of such features include (but are not limited to) nested classes and interfaces, record classes, and enum classes.
Nested classes and interfaces
Nested classes are classes declared within the body of another class or interface declaration. Nested classes may be static or non-static member classes, local classes, or anonymous classes. Some kinds of nested classes that are not explicitly or implicitly?static?(and therefore can refer to instances of the enclosing classes) are inner classes.
Code examples
The example classes are a top-level class with a non-static member class.
class OuterClass {
class InnerClass {
}
}
The following code shows which synthetic field (a reference to an instance of the enclosing class) and constructor (to set that field) the Java compiler generates for that inner class.
Class<?> clazz = SomeOuterClass.SomeInnerClass.class;
Field[] fields = clazz.getDeclaredFields();
assertEquals(1, fields.length);
Field field = fields[0];
assertEquals("/* synthetic */ final demo.SomeOuterClass demo.SomeOuterClass$SomeInnerClass.this$0", toString(field));
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
assertEquals(1, constructors.length);
Constructor<?> constructor = constructors[0];
assertEquals("demo.SomeOuterClass$SomeInnerClass(demo.SomeOuterClass)", toString(constructor));
So these are the actual classes that the Java compiler generates from that non-static member class:
class SomeOuterClass {
class SomeInnerClass {
/* synthetic */ final SomeOuterClass this$0;
SomeInnerClass(SomeOuterClass this$0) {
this.this$0 = this$0;
}
}
}
Record classes
Record classes are restricted kinds of classes that define an immutable aggregate of values. From record components in the record header, the Java compiler created the following implicit constructs:
Code examples
The example class is a record class with one record component.
record SomeRecord(int i) {
}
The following code shows which implicit fields, methods, and constructors the Java compiler generates for the record class.
Class<?> clazz = SomeRecord.class;
assertTrue(Modifier.isFinal(clazz.getModifiers()));
assertEquals(Record.class, clazz.getSuperclass());
Field[] fields = clazz.getDeclaredFields();
assertEquals(1, fields.length);
Field field = fields[0];
assertEquals("private final int demo.SomeRecord.i", toString(field));
Method[] methods = clazz.getDeclaredMethods();
assertEquals(
Set.of(
"public int demo.SomeRecord.i()",
"public final boolean demo.SomeRecord.equals(java.lang.Object)",
"public final int demo.SomeRecord.hashCode()",
"public final java.lang.String demo.SomeRecord.toString()"
),
toStringSet(methods)
);
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
assertEquals(1, constructors.length);
Constructor<?> constructor = constructors[0];
assertEquals("demo.SomeRecord(int)", toString(constructor));
So this is the actual class that the Java compiler generates from this record class:
final class SomeRecord extends Record {
private final int i;
SomeRecord(int i) {
this.i = i;
}
public int i() {
return this.i;
}
@Override
public final boolean equals(Object obj) {...}
@Override
public final int hashCode() {...}
@Override
public final String toString() {...}
}
Enum classes
Enum classes are restricted kinds of classes that define a fixed set of named class instances (enum constants).
Code examples
The example class is an enum class with one enum constant without a class body.
enum SomeEnum {
INSTANCE
}
The following code shows which implicit and synthetic fields, methods, and constructors the Java compiler generates for the enum class.
Class<?> clazz = SomeEnum.class;
assertEquals(Enum.class, clazz.getSuperclass());
Field[] fields = clazz.getDeclaredFields();
assertEquals(
Set.of(
"public static final demo.SomeEnum demo.SomeEnum.INSTANCE",
"/* synthetic */ private static final demo.SomeEnum[] demo.SomeEnum.$VALUES"
),
toStringSet(fields)
);
Method[] methods = clazz.getDeclaredMethods();
assertEquals(
Set.of(
"public static demo.SomeEnum[] demo.SomeEnum.values()",
"public static demo.SomeEnum demo.SomeEnum.valueOf(java.lang.String)",
"/* synthetic */ private static demo.SomeEnum[] demo.SomeEnum.$values()"
),
toStringSet(methods)
);
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
assertEquals(1, constructors.length);
Constructor<?> constructor = constructors[0];
assertEquals("private demo.SomeEnum(java.lang.String,int)", toString(constructor));
So this is the actual class that the Java compiler generates from this enum class:
final class SomeEnum extends Enum<SomeEnum> {
public static final SomeEnum INSTANCE = new SomeEnum("INSTANCE", 0);
/* synthetic */ private static final SomeEnum[] $VALUES;
public static SomeEnum[] values() {
return (SomeEnum[])$VALUES.clone();
}
public static SomeEnum valueOf(String name) {
return Enum.valueOf(SomeEnum.class, name);
}
private SomeEnum(String string, int n) {
super(string, n);
}
/* synthetic */ private static SomeEnum[] $values() {
return new SomeEnum[]{INSTANCE};
}
static {
$VALUES = SomeEnum.$values();
}
}
Conclusion
The?Class?class is the entry point of the Core Reflection API. Once you get the?Class?object for a class or interface, you can discover and use its fields, methods, constructors, member classes and member interfaces.
In typical cases, reflective access for members and constructors consists of getting and setting field values in various serializing/deserializing operations, bypassing access control for fields and methods in legacy code, and creating new instances at runtime when the exact class is unknown at compile time.
Complete code examples are available in the?GitHub repository.