Previous in the Series
Current Tutorial
Invoking Methods
Next in the Series

Previous in the Series: Reading and Writing Fields

Next in the Series: Invoking Constructors

Invoking Methods

A Method object let you get more information on the corresponding method: its type and its modifiers, the types and names of its parameters, and enables you to invoke it on a given object, passing the arguments you need. This section also covers how you can discover methods in a class.

 

Locating Methods

There are two categories of methods provided in Class for accessing methods.

  1. First, you can look for a specific method. These methods suppose that you have a name for the method you are looking for, and the type of its parameters.
  2. Second, you can look for all the methods that are declared in this class, or for the methods declared in this class, and all its super classes, up to the Object class. In the first case, you will get all the methods: public, protected, default (package) access, and private. In the second case, you will get only the public methods.

The following tables provide a summary of all the member-locating methods and their characteristics.

Class API Array of methods? Inherited methods? Private methods?
getDeclaredMethod(String, Object...) no no yes
getMethod(String, Object...) no yes no
getDeclaredMethods() yes no yes
getMethods() yes yes no

Let us see this in action. Suppose that you have the following class.

public class User {
    private final String name;
    
    public User(String name) {
        validate(name);
        this.name = name;
    }

    private void validate(String name) {
        Objects.requireNonNull(name);
    }
    
    public String getName() {
        return name;
    }
    
   @Override
   public String toString() {
       return "User[" + "name=" + name + "]";
   }
}

First, let us run the following code, that gets all the public methods from the User class and all of its super classes.

Class<?> c = User.class;
Method[] methods = c.getMethods();
for (Method method : methods) {
    System.out.println("method = " + method);
}

Running the previous code prints the following. As you can see, there are the two methods from User: getName() and toString(), and the methods from the Object class, apart from the toString() method, which is defined in the User class. If you remove the toString() method from the User class, then you will see the toString() from the Object class in this list.

method = public java.lang.String org.devjava.User.getName()
method = public java.lang.String org.devjava.User.toString()
method = public boolean java.lang.Object.equals(java.lang.Object)
method = public native int java.lang.Object.hashCode()
method = public final native java.lang.Class java.lang.Object.getClass()
method = public final native void java.lang.Object.notify()
method = public final native void java.lang.Object.notifyAll()
method = public final void java.lang.Object.wait(long) throws java.lang.InterruptedException
method = public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
method = public final void java.lang.Object.wait() throws java.lang.InterruptedException

Let us now run the following code, that is calling Class.getDeclaredMethods() to get all the methods from the User class.

Class<?> c = User.class;
Method[] methods = c.getDeclaredMethods();
for (Method method : methods) {
    System.out.println("method = " + method);
}

Running the previous code prints the following. This time, only the methods declared in the User class are present, including the private methods.

method = public java.lang.String org.devjava.User.getName()
method = public java.lang.String org.devjava.User.toString()
method = private void org.devjava.User.validate(java.lang.String)

 

Obtaining the Return Type of a Method

The return type of a method is modeled in the same way as the type of a Field. The Method.getReturnType() method gives you the type as a Class object, and the Method.getGenericReturnType() returns a Type object from which you can get information about the generic type.

Let us see these two methods in action on the List interface.

First, you can get a reference on the method List.get(int). This method returns the element of the list with the index you passed as an argument. The return type of this method is the parameter type of the list you declared.

Note that we used the class int.class to denote the int type of the parameter that the List.get(index) method takes. Using the Integer.class type would have thrown a NoSuchMethodException.

Class<?> listClass = List.class;
Method getWithIndex = listClass.getMethod("get", int.class);

Class<?> returnType = getWithIndex.getReturnType();
System.out.println("Return type = " + returnType);

Type genericReturnType = getWithIndex.getGenericReturnType();
System.out.println("Generic return type = " + genericReturnType);

Running the previous example gives you the following.

Return type = class java.lang.Object
Generic return type = E

As you can see, the Method.getReturnType() gives you the raw type that this method returned, whereas the Method.getGenericReturnType() gives you the information that it is in fact the parameter type of the List interface.

Let us test it with another method: List.of().

Class<?> listClass = List.class;
Method listOf = listClass.getMethod("of", Object.class);

Class<?> returnType = listOf.getReturnType();
System.out.println("Return type = " + returnType);

Type genericReturnType = listOf.getGenericReturnType();
System.out.println("Generic return type = " + genericReturnType);

Running this example gives you the following.

Return type = interface java.util.List
Generic return type = java.util.List<E>

 

Obtaining the Parameters of a Method

The Method class gives you access to the parameters of a given method, including their names.

Obtaining the Names of Method Parameters

You can obtain the names of the formal parameters of any method or constructor (that are covered in the next section) with the method getParameters(). The classes Method and Constructor extend the class Executable and therefore inherit this method. However, .class files do not store formal parameter names by default. This is because many tools that produce and consume class files may not expect the larger static and dynamic footprint of .class files that contain parameter names. In particular, these tools would have to handle larger .class files, and the Java Virtual Machine (JVM) would use more memory. In addition, some parameter names, such as secret or password, may expose information about security-sensitive methods.

To store formal parameter names in a particular .class file, and thus enable the Reflection API to retrieve formal parameter names, compile the source file with the -parameters option to the javac compiler. You can find more information the javac compiler in this section.

Obtaining the Return Type of a Method

The return type of a method is modeled in the same way as the type of a Field. The Method.getReturnType() method gives you the type as a Class object, and the Method.getGenericReturnType() returns a Type from which you can get information about the generic type.

 

Obtaining the Exceptions of a Method

The Method class gives you access to the declared exceptions of a method. These exceptions are returned in an array, which can be of two types: an array of Class instances, or an array of Type instances. As usual the Type object can give you information about a parameter type.

Let us discover the numerous exceptions thrown by the Constructor.newInstance() method, that is part of the Reflection API. Note how you can denote an array of Object type, with the Object[].class syntax.

Class<?> constructorClass = Constructor.class;
Method newInstance = constructorClass.getMethod("newInstance", Object[].class);

var exceptionTypes = newInstance.getExceptionTypes();
System.out.println("exceptionTypes:");
for (var exceptionType : exceptionTypes) {
    System.out.println("   exceptionType = " + exceptionType);
}

Running this example produces the following:

Exception types:
   Exception type = class java.lang.InstantiationException
   Exception type = class java.lang.IllegalAccessException
   Exception type = class java.lang.IllegalArgumentException
   Exception type = class java.lang.reflect.InvocationTargetException

You can run the same code using the other pattern, that returns an array of Type objects. Note that in that case, you are calling Method.getGenericExceptionTypes().

Class<?> constructorClass = Constructor.class;
Method newInstance = constructorClass.getMethod("newInstance", Object[].class);
var exceptionTypes = newInstance.getGenericExceptionTypes();
System.out.println("Exception types:");
for (var exceptionType : exceptionTypes) {
        System.out.println("   Exception type = " + exceptionType);
}

The result printed on the console is the same.

Exception types:
   Exception type = class java.lang.InstantiationException
   Exception type = class java.lang.IllegalAccessException
   Exception type = class java.lang.IllegalArgumentException
   Exception type = class java.lang.reflect.InvocationTargetException

 

Retrieving Method Modifiers

The modifiers of a method work in the same way as the modifiers of a class or a field. The method Method.getModifiers() returns an int in which each bit represent a single modifier. For instance, if the bit 0 is set, it means that this method is public. If the bit 3 is set, then this method is static.

You can read the modifiers of the String.join() method with the following code.

Class<?> stringClass = String.class;
Method join = stringClass.getMethod("join", CharSequence.class, Iterable.class);

int modifiers = join.getModifiers();

System.out.println("is static? " + Modifier.isStatic(modifiers));
System.out.println("is public? " + Modifier.isPublic(modifiers));

This code prints the following on the console.

is static? true
is public? true

You can also use the access flags, covered in this section

Class<?> stringClass = String.class;
Method join = stringClass.getMethod("join", CharSequence.class, Iterable.class);
Set<AccessFlag> accessFlags = join.accessFlags();
System.out.println("Access flags: " + accessFlags);

This code prints the following on the console.

Access flags: [PUBLIC, STATIC]

 

Invoking a Method

So far you learnt that a method can be modeled by an object instance of the Method class, and that you can use this object to get information on the method it models, including its name, parameters, return type, and the exceptions this method may declare.

Using this object, you can also invoke this method, passing it arguments and getting its result. If it throws exceptions, you can also catch them and analyze them. This feature of the Method class is widely used in many frameworks, because it allows you to execute a method from elements that are discoverable.

A method can be invoked using its invoke() method, that takes two parameters.

  • The first one is the object on which you want to invoke the method.
  • The second one is actually a vararg, so you can pass any number of arguments with this declaration, even no argument. This vararg receives the parameters you need to pass to the method you are invoking. You can get more information on this variable argument feature on this page.

If the method you are invoking does not take any parameter, then you need to call the invoke() method with only one argument: the object on which you are invoking this method. On the other hand, if the method you are invoking takes two parameters, then you invocation of the invoke() method needs three arguments: the object on which you are invoking this method, and the two arguments this method takes.

Getting the Result of an Invoked Method

Let us start with a simple example. Suppose you want to get the length of a string of characters using the Reflection API. The String.length() does not take any parameter, which make the invocation of this method easier. All you need to do is to call the invoke() method of the Method class, passing it the string on which you want to invoke the length() method.

String hello = "Hello";

Class<?> stringClass = String.class;
Method lengthMethod = stringClass.getMethod("length");

int length = (int)lengthMethod.invoke(hello);
System.out.println("length = " + length);

Note that the getMethod() method returns a type Object, meaning that you need to cast the return object to the correct type, if you know it. Running this code prints the following:

length = 5

Passing Parameters to the Invoked Method

The objects and values you need to pass to the method you invoke can be passed to invoke() as further arguments. In the following example, we are calling the substring() method, that takes two integers. Note that there are two overloads of the String.substring() method, one that takes one int, and the other that takes two. Here we are getting the second overload, by passing the two int.class arguments.

String hello = "Hello world!";
Class<?> stringClass = String.class;

Method substringMethod = stringClass.getMethod("substring", int.class, int.class);
String substring = (String)substringMethod.invoke(hello, 6, 11);
System.out.println("substring = " + substring);

The previous code prints the following.

substring = world

Getting the Thrown Exceptions

There are cases where the method you invoke using the Reflection API may throw exceptions that you need to be aware of. The Reflection API catches these exceptions for you, and wrap them in a InvocationTargetException that you can catch and analyze.

Suppose you need to open a file for writing using the Reflection API, but pass a file that cannot be opened. You can do that with the following code.

Note that we are using the Files.newBufferedWriter() method, that declares a variable argument. To get such a method, you need to consider this variable argument as an array, and pass an empty array of the correct type to the invoke() call.

Note also that the Files.newBufferedWriter() is static. So you do not invoke it on any object. In that case, the first argument passed to the invoke() method is null.

Path doesNotExist = Path.of("/does/not/exist");
Class<?> filesClass = Files.class;
Method newBufferedWriterMethod = 
        filesClass.getMethod("newBufferedWriter", Path.class, OpenOption[].class);
try (Writer writer = (Writer)newBufferedWriterMethod.invoke(null, doesNotExist, new OpenOption[0]);) {
    writer.write("Hello");
} catch (InvocationTargetException ite) {
    Throwable targetException = ite.getTargetException();
    System.out.println("ite.getTargetException() = " + targetException);
    System.out.println("Stack trace:");
    targetException.printStackTrace();
} catch (IOException e) {
    throw new RuntimeException(e);
}

The opening and the closing of the writer (which is done by the use of this try-with-resources statement) throws an IOException that is caught by the Reflection API, and wrapped in a InvocationTargetException. The InvocationTargetException class has a getTargetException() method that gives you this exception. We are using this pattern in this example. Running it will give you the following.

ite.getTargetException() = java.nio.file.NoSuchFileException: \does\not\exist
Stack trace:
java.nio.file.NoSuchFileException: \does\not\exist
    // this part of the stack trace depends on your file system
    at java.base/java.nio.file.spi.FileSystemProvider.newOutputStream(FileSystemProvider.java:484)
    at java.base/java.nio.file.Files.newOutputStream(Files.java:228)
    at java.base/java.nio.file.Files.newBufferedWriter(Files.java:3007)
    at java.base/java.nio.file.Files.newBufferedWriter(Files.java:3055)
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
    at java.base/java.lang.reflect.Method.invoke(Method.java:578)
    at org.devjava.Main.main(Main.java:48)

 

Accessing Private Methods

Trying to invoke a private method from outside the class, or any other case of a non-visible method will throw a IllegalAccessException.

You can still get access to such a method and invoke it, by first calling method.setAccessible(true) on it. This call may fail in case you are trying to invoke a method from a class in another module, due to the restrictions on the class members a module. You can check the page Reflective Access with Open Modules and Open Packages for more information.

Note that calling setAccessible(true) on a method does not make this method public. It just suppresses the checks for access control.

 

Generics and Type Erasure

You need to keep in mind that the discovery of a method depends on how this method has been compiled. The type parameter of a method is erased at compile time, meaning that you cannot use it with the Reflection API.

Consider the following example, where you want to access the add() method of a List object.

List<String> strings = new ArrayList<>();

Class<?> stringsClass = strings.getClass();
Method addString = stringsClass.getMethod("add", String.class);

Here you are trying to get a reference on the add() method of the class of an object of type List<String>. This method declares a String parameter. But because this parameter has in fact the type E (that receives the value String in this example), it is erased, and has in fact the type Object.

So running this code produces the following result, because there is no add() method on this class, that takes a String as a parameter.

Exception in thread "main" java.lang.NoSuchMethodException: java.util.ArrayList.add(java.lang.String)
    at java.base/java.lang.Class.getMethod(Class.java:2277)
    at org.devjava.Main.main(Main.java:47)

The correct code is the following, that looks for a method that declares a parameter of type Object:

List<String> strings = new ArrayList<>();

Class<?> stringsClass = strings.getClass();
Method addString = stringsClass.getMethod("add", Object.class);

You can then invoke this method, passing a string to it. But by doing so you loose the type safety of calling the method directly.


Last update: July 19, 2024


Previous in the Series
Current Tutorial
Invoking Methods
Next in the Series

Previous in the Series: Reading and Writing Fields

Next in the Series: Invoking Constructors