Java Reflection, part 2: fields, methods, constructors
Photo by Samuel Pagel on Unsplash

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:

No alt text provided for this image

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.

code examples

Using members and constructors

The?Member?interface and its implementations have the following type hierarchy, which represents reflected fields, methods, and constructors.

No alt text provided for this image

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 the?Class?object representing the class or interface that declares the member or constructor
  • String getName()?- returns the simple name of the reflected member or constructor
  • int getModifiers()?- returns the Java language modifiers for the member or constructor
  • boolean isSynthetic()?- returns?true?if and only if this member or constructor was introduced by the Java compiler

code examples

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)?- returns?true?if and only if the caller can access this reflected object
  • void setAccessible(boolean flag)?- sets the?accessible?flag for this reflected object to the given boolean value and throws?InaccessibleObjectException?if the access control cannot be suppressed
  • boolean trySetAccessible()?- sets the?accessible?flag for this reflected object to?true?and returns the?true?if the access control is suppressed and?false?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
}        

code examples

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 a?Class?object that identifies the declared type for the field
  • Object 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));        

more code examples

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 of?Parameter?objects representing all the parameters
  • abstract Class<?>[] getParameterTypes()?- returns an array of?Class?objects that represent the formal parameter types
  • abstract Class<?>[] getExceptionTypes()?- returns an array of?Class?objects that represent the types of thrown exceptions
  • boolean isVarArgs()?- returns?true?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 a?Class?object that represents the formal return type of the method
  • Object invoke(Object obj, Object... args)?- invokes the reflected method represented by this?Method?object on the specified object with the specified parameters
  • boolean isBridge()?- returns?true?if and only if this method is a bridge method
  • boolean isDefault()?- returns?true?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));        

more code examples

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);        

more code examples

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 the?Executable?declaring this parameter
  • boolean isNamePresent()?- returns?true?if and only if the parameter has a name according to the?class?file
  • String getName()?- returns the name of the parameter
  • int getModifiers()?- returns the Java language modifiers for the parameter
  • Class<?> getType()?- returns a?Class?object that identifies the declared type for the parameter
  • boolean isSynthetic()?- returns?true?if and only if this parameter is neither explicitly nor implicitly declared in the source code
  • boolean isVarArgs()?- returns?true?if and only if this method is declared to take a variable number of arguments
  • boolean isImplicit()?- returns?true?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());        

more code examples

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?and?toString

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.

要查看或添加评论,请登录

Aliaksandr Liakh的更多文章

社区洞察

其他会员也浏览了