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 superclasses 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:
Class<?> getDeclaringClass()
- returns theClass
object representing the class or interface that declares the member or constructorString getName()
- returns the simple name of the reflected member or constructorint getModifiers()
- returns the Java language modifiers for the member or constructorboolean isSynthetic()
- returnstrue
if and only if this member or constructor was introduced by the Java compiler
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:
boolean canAccess(Object obj)
- returnstrue
if and only if the caller can access this reflected objectvoid setAccessible(boolean flag)
- sets theaccessible
flag for this reflected object to the given boolean value and throwsInaccessibleObjectException
if the access control cannot be suppressedboolean trySetAccessible()
- sets theaccessible
flag for this reflected object totrue
and returns thetrue
if the access control is suppressed andfalse
otherwise
By default, Java language access control allows the use of:
- private members only inside their top-level class
- package members only inside their package
- protected members only inside their package or subclasses
- public members outside their module only if they are declared in an exported package, and the caller module requires their module
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:
- the target class is public, the member is public, and the target module use the
exports
directive from the target package to the caller module - the target class is public, the member is protected static, the caller class is a subclass of the target class, and the target module use the
exports
directive from the target package to the caller module - the target module use the
opens
directive from the target package to the caller module - the target class is in an unnamed or open module
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:
Class<?> getType()
- returns aClass
object that identifies the declared type for the fieldObject get(Object obj)
- returns the value of the field on the specified object(*)
void set(Object obj, Object value)
- sets the field to the specified new value on the specified object(*)
(*) 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:
int getParameterCount()
- returns the number of formal parameters (whether explicitly declared or implicitly declared or neither)Parameter[] getParameters()
- returns an array ofParameter
objects representing all the parametersabstract Class<?>[] getParameterTypes()
- returns an array ofClass
objects that represent the formal parameter typesabstract Class<?>[] getExceptionTypes()
- returns an array ofClass
objects that represent the types of thrown exceptionsboolean isVarArgs()
- returnstrue
if and only if this method is declared to take a variable number of arguments
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:
Class<?> getReturnType()
- returns aClass
object that represents the formal return type of the methodObject invoke(Object obj, Object... args)
- invokes the reflected method represented by thisMethod
object on the specified object with the specified parametersboolean isBridge()
- returnstrue
if and only if this method is a bridge methodboolean isDefault()
- returnstrue
if and only if this method is a default method
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:
T newInstance(Object... args)
- creates and initializes a new instance of the declaring class using the reflected constructor with the specified parameters
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:
Executable getDeclaringExecutable()
- returns theExecutable
declaring this parameterboolean isNamePresent()
- returnstrue
if and only if the parameter has a name according to the class fileString getName()
- returns the name of the parameterint getModifiers()
- returns the Java language modifiers for the parameterClass<?> getType()
- returns aClass
object that identifies the declared type for the parameterboolean isSynthetic()
- returnstrue
if and only if this parameter is neither explicitly nor implicitly declared in the source codeboolean isVarArgs()
- returnstrue
if and only if this method is declared to take a variable number of argumentsboolean isImplicit()
- returnstrue
if and only if this parameter is implicitly declared in the source code
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:
- a private final field and a public accessor method (for each record component in the record header)
- a canonical constructor whose signature is the same as the record header
- public final methods
equals
,hashCode
andtoString
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.