A class method extractor
You’ll rarely need to use the reflection tools directly; they’re in the language to support other Java features, such as object serialization (Chapter 12) and JavaBeans (Chapter 14). However, there are times when it’s quite useful to be able to dynamically extract information about a class. One extremely useful tool is a class method extractor. As mentioned before, looking at a class definition source code or JDK documentation shows only the methods that are defined or overridden within that class definition. But there could be dozens more available to you that have come from base classes. To locate these is both tedious and time consuming.[50] Fortunately, reflection provides a way to write a simple tool that will automatically show you the entire interface. Here’s the way it works:
//: c10:ShowMethods.java
// Using reflection to show all the methods of a class,
// even if the methods are defined in the base class.
// {Args: ShowMethods}
import java.lang.reflect.*;
import java.util.regex.*;
public class ShowMethods {
private static final String usage =
"usage: \n" +
"ShowMethods qualified.class.name\n" +
"To show all methods in class or: \n" +
"ShowMethods qualified.class.name word\n" +
"To search for methods involving 'word'";
private static Pattern p = Pattern.compile("\\w+\\.");
public static void main(String[] args) {
if(args.length < 1) {
System.out.println(usage);
System.exit(0);
}
int lines = 0;
try {
Class c = Class.forName(args[0]);
Method[] m = c.getMethods();
Constructor[] ctor = c.getConstructors();
if(args.length == 1) {
for(int i = 0; i < m.length; i++)
System.out.println(
p.matcher(m[i].toString()).replaceAll(""));
for(int i = 0; i < ctor.length; i++)
System.out.println(
p.matcher(ctor[i].toString()).replaceAll(""));
lines = m.length + ctor.length;
} else {
for(int i = 0; i < m.length; i++)
if(m[i].toString().indexOf(args[1]) != -1) {
System.out.println(
p.matcher(m[i].toString()).replaceAll(""));
lines++;
}
for(int i = 0; i < ctor.length; i++)
if(ctor[i].toString().indexOf(args[1]) != -1) {
System.out.println(p.matcher(
ctor[i].toString()).replaceAll(""));
lines++;
}
}
} catch(ClassNotFoundException e) {
System.out.println("No such class: " + e);
}
}
} ///:~
The Class methods getMethods( ) and getConstructors( ) return an array of Method and array of Constructor, respectively. Each of these classes has further methods to dissect the names, arguments, and return values of the methods they represent. But you can also just use toString( ), as is done here, to produce a String with the entire method signature. The rest of the code extracts the command line information, determines if a particular signature matches your target string (using indexOf( )), and strips off the name qualifiers.
To strip the name qualifiers like “java.lang.” from “java.lang.String,” Java JDK 1.4 regular expressions offer a powerful and succinct tool that has been available in some languages for many years. You’ve already seen simple usage of regular expressions inside the expect( ) statements of the com.bruceeckel.simpletest.Test class. In the preceding example, you can see the basic coding steps necessary to use regular expressions in your own programs.
After importing java.util.regex, you first compile the regular expression by using the static Pattern.compile( ) method, which produces a Pattern object using the string argument. In this case, the argument is
"\\w+\\."
To understand this or any other regular expression, look at the JDK documentation under java.util.regex.Pattern. For this one, you’ll find that ‘\w’ means “a word character: [a-zA-Z_0-9].” The ‘+’ means “one or more of the preceding expression”—so in this case, one or more word characters—and the ‘\.’ produces a literal period (rather than the period operator, which means “any character” in a regular expression). So this expression will match any sequence of word characters followed by a period, which is exactly what we need to strip off the qualifiers.
After you have a compiled Pattern object, you use it by calling the matcher( ) method, passing the string that you want to search. The matcher( ) method produces a Matcher object, which has a set of operations to choose from (you can see all of these in the JDK documentation for java.util.regex.Matcher). Here, the replaceAll( ) method is used to replace all the matches with empty strings—that is, to delete the matches.
As a more compact alternative, you can use the regular expressions built into the String class. For example, the last use of replaceAll( ) in the preceding program could be rewritten from:
p.matcher(ctor[i].toString()).replaceAll("")
to
ctor[i].toString().replaceAll("\\w+\\.", "")
without precompiling the regular expression. This form is good for single-shot uses of regular expressions, but the precompiled form is significantly more efficient if you need to use the regular expression more than once, as is the case with this example.
This example shows reflection in action, since the result produced by Class.forName( ) cannot be known at compile time, and therefore all the method signature information is being extracted at run time. If you investigate the JDK documentation on reflection, you’ll see that there is enough support to actually set up and make a method call on an object that’s totally unknown at compile time (there will be examples of this later in this book). Although initially this is something you may not think you’ll ever need, the value of full reflection can be quite surprising.
An enlightening experiment is to run
java ShowMethods ShowMethods
This produces a listing that includes a public default constructor, even though you can see from the code that no constructor was defined. The constructor you see is the one that’s automatically synthesized by the compiler. If you then make ShowMethods a non-public class (that is, package access), the synthesized default constructor no longer shows up in the output. The synthesized default constructor is automatically given the same access as the class.
Another interesting experiment is to invoke java ShowMethods java.lang.String with an extra argument of char, int, String, etc.
This tool can be a real time-saver while you’re programming, when you can’t remember if a class has a particular method and you don’t want to go hunting through the index or class hierarchy in the JDK documentation, or if you don’t know whether that class can do anything with, for example, Color objects.
Chapter 14 contains a GUI version of this program (customized to extract information for Swing components) so you can leave it running while you’re writing code, to allow quick lookups.