25.2. Finding and Using Methods and Fields

. Problem

You need more to find arbitrary method or field names in arbitrary classes.

. Solution

Use the reflection package java.lang.reflect.

. Discussion

If you just wanted to find fields and methods in one particular class, you wouldn't need this recipe; you could simply create an instance of the class using new and refer to its fields and methods directly. But this allows you to find methods and fields in any class, even classes that have not yet been written! Given a class object created as in Recipe 25.1, you can obtain a list of constructors, a list of methods, or a list of fields. The method getMethods( ) lists the methods available for a given class as an array of Method objects. Similarly, getFields( ) returns a list of Field objects. Since constructor methods are treated specially by Java, there is also a getConstructors( ) method, which returns an array of Constructor objects. Even though "class" is in the package java.lang, the Constructor, Method, and Field objects it returns are in java.lang.reflect, so you need an import of this package. The ListMethods class (Example 25-1) shows how get a list of methods in a class whose name is known at runtime.

For example, you could run Example 25-1 on a class like java.lang.String and get a fairly lengthy list of methods; I'll only show part of the output so you can see what it looks like:

> java ListMethods java.lang.String
*** Constructors ***
public java.lang.String( )
public java.lang.String(java.lang.String)
public java.lang.String(java.lang.StringBuffer)
public java.lang.String(byte[])
// and many more...
*** Methods ***
public static java.lang.String java.lang.String.copyValueOf(char[])
public static java.lang.String java.lang.String.copyValueOf(char[],int,int)
public static java.lang.String java.lang.String.valueOf(char)
// and more valueOf( ) forms...
public boolean java.lang.String.equals(java.lang.Object)
public final native java.lang.Class java.lang.Object.getClass( )
// and more java.lang.Object methods...
public char java.lang.String.charAt(int)
public int java.lang.String.compareTo(java.lang.Object)
public int java.lang.String.compareTo(java.lang.String)

You can see that this could be extended (almost literally) to write a BeanMethods class that would list only the set/get methods defined in a JavaBean (see Recipe 23.8).

Alternatively, you can find a particular method and invoke it, or find a particular field and refer to its value. Let's start by finding a given field, since that's the easiest. Example 25-2 is code that, given an Object and the name of a field, finds the field (gets a Field object) and then retrieves and prints the value of that Field as an int.

What if we need to find a method? The simplest way is to use the methods getMethod( ) and invoke( ). But this is not altogether trivial. Suppose that somebody gives us a reference to an object. We don't know its class but have been told that it should have this method:

public void work(String s) { }

We wish to invoke work( ). To find the method, we must make an array of Class objects, one per item in the calling list. So, in this case, we make an array containing only a reference to the class object for String. Since we know the name of the class at compile time, we'll use the shorter invocation String.class instead of Class.forName( ). This, plus the name of the method as a string, gets us entry into the getMethod( ) method of the Class object. If this succeeds, we have a Method object. But guess what? In order to invoke the method, we have to construct yet another array, this time an array of Object references actually containing the data to be passed to the invocation. We also, of course, need an instance of the class in whose context the method is to be run. For this demonstration class, we need to pass only a single string, as our array consists only of the string. Example 25-3 is the code that finds the method and invokes it.

Not tiny, but it's still not bad. In most programming languages, you couldn't do that in the 40 lines it took us here.

A word of caution: when the arguments to a method are of a primitive type, such as int, you do not pass Integer.class into getMethod( ). Instead, you must use the class object representing the primitive type int. The easiest way to find this class is in the Integer class, as a public constant named TYPE, so you'd pass Integer.TYPE. The same is true for all the primitive types; for each, the corresponding wrapper class has the primitive class referred to as TYPE.