Take an in-depth look at the Java Reflection API

Learn about the new Java 1.1 tools for finding out information about classes

1 2 Page 2
Page 2 of 2

Once these three loops have completed, the method is ready to generate the import statements for the class. This is done simply by enumerating the hashtable and printing out the keys, as shown below.

        // Don't import ourselves ...
        classRef.remove(c.getName());
        for (Enumeration e = classRef.keys(); e.hasMoreElements(); ) {
            System.out.println("import "+e.nextElement()+";");
        }
        System.out.println();

Another feature of the above code is that it removes the name of the base class to prevent generating an import statement for this class.

Generate the class or interface declarations

Now with the imports out of the way, the code can generate the outline for the class itself. The header of the class always takes the form:

    [ optional modifiers (public private etc.)]
    class/interface ClassName extends ClassName
    {

The code that generates this is shown next.

        int mod = c.getModifiers();
        System.out.print(Modifier.toString(mod));
        if (Modifier.isInterface(mod)) {
            System.out.print(" interface ");
        } else {
            System.out.print(" class ");
        }
        System.out.print(tName(c.getName(), null));
        supClass = c.getSuperclass();
        if (supClass != null) {
            System.out.print(" extends "+tName(supClass.getName(), classRef));
        }
        System.out.println(" {");

Walking through the code above, the first thing it does is collect the modifiers. These are then printed using a method in the Modifier class. That method prints out all of the modifiers that make sense for a type declaration such as public, private, static, and so on. Next I've used the isInterface method to check to see if the class I've loaded is an interface. If it isn't, I print out the keyword class; otherwise, I print out the keyword interface. Then comes the name, which is followed by the extends keyword and the name of this class's superclass. The code finishes up by printing the opening curly brace.

Generate the field (variable) declarations

Variables that are declared within the class -- both static variables (class variables) and non-static variables (instance variables) -- are then printed. Whether they are static or dynamic, variable declarations in the class are called fields.

After the class declaration has been printed, the ReflectClass class prints out all of the fields that the class defines. Throughout the code, the tName method is being used to provide the shorthand versions of the type names. The fields are printed with the following code:

        System.out.println("\n\r/*\n\r * Field Definitions.\r\n */");
        for (int i = 0; i < ff.length; i++) {
            Class ctmp = ff[i].getType();
            int md     = ff[i].getModifiers();
            System.out.println("    "+Modifier.toString(md)+" "+
                    tName(ff[i].getType().getName(), null) +" "+
                    ff[i].getName()+";");
        }

The type of the field is collected using the getType method. The field is then printed, modifiers first, then the type name (int, FooClass, and so on), and finally the name of the field.

Generate the constructor declarations

After the fields, I've chosen to print out the constructors. This primarily is a convention that I use when I write my own Java code, but there isn't anything special about the printing order.

        
        System.out.println("\n\r/*\n\r * Declared Constructors. \n\r */");
        x = tName(c.getName(), null);
        for (int i = 0; i < cn.length; i++) {
            int md = cn[i].getModifiers();
            System.out.print("    " + Modifier.toString(md) + " " + x);
            Class cx[] = cn[i].getParameterTypes();
            System.out.print("( ");
            if (cx.length > 0) {
                for (int j = 0; j < cx.length; j++) {
                    System.out.print(tName(cx[j].getName(), null));
                    if (j < (cx.length - 1)) System.out.print(", ");
                }
            }
            System.out.print(") ");
            System.out.println("{ ... }");
        }

The above code is a bit more complicated because, in addition to the name, a set of optional parameters have to be printed out as well. Note that if the parameter length was 0, then the code prints "( )". Finally, a pair of curly braces are printed to indicate where the code would go in the actual source.

Generate the method declarations

The final chunk of code prints out the method declarations and their parameters. This code is shown here:

        System.out.println("\n\r/*\n\r * Declared Methods.\n\r */");
        for (int i = 0; i < mm.length; i++) {
            int md = mm[i].getModifiers();
            System.out.print("    "+Modifier.toString(md)+" "+
                    tName(mm[i].getReturnType().getName(), null)+" "+
                    mm[i].getName());
            Class cx[] = mm[i].getParameterTypes();
            System.out.print("( ");
            if (cx.length > 0) {
                for (int j = 0; j < cx.length; j++) {
                    System.out.print(tName(cx[j].getName(), classRef));
                    if (j < (cx.length - 1)) System.out.print(", ");
                }
            }
            System.out.print(") ");
            System.out.println("{ }");
        }

The additional complexity in the above code comes from printing both a return type, like the fields, and a list of parameters, like the constructors.

When I ran ReflectClass and fed it the name of the ReflectClass class, it produced the following output:

import java.util.Hashtable;
import java.lang.String;
public synchronized class ReflectClass extends Object {
/*
 * Field Definitions.
 */
/*
 * Declared Constructors. 
 */
    public ReflectClass( ) { ... }
/*
 * Declared Methods.
 */
    static String tName( String, Hashtable) { ... }
    public static void main( String[]) { ... }
}

The complete source to the ReflectClass class is available in the Resources section.

Going further

The Reflection API is a powerful tool for inspecting classes. Further, unlike last month where we were left only with information about the class, the methods represented by the Method class and the constructors represented by the Constructor class can actually be invoked. Unlike the situation last month, however, we are unable to modify the classes before they are actually loaded, and there is no access to attributes in the class file itself. Thus, there are times when introspection through file inspection is more useful than introspection through reflection. The Reflection API does solve that long-standing problem with dynamic Java execution of creating an instance of a dynamically loaded class that did not have a null constructor.

The issue with null constructors can be stated as follows, "The newInstance method in class Class implicitly invokes the public constructor in the object that takes no parameters (the null constructor)." If you attempt to dynamically load a class with the forName method of class Class, and it does not have an accessible null constructor, historically there has been no way to create an instance of that class. With the Reflection API you can get the constructors with the getConstructors method of class Class, and, using a public constructor, invoke it using the newInstance method of the Constructor class. Constructor's newInstance method takes an array of objects that supply the arguments to non-null constructors.

This ability to invoke arbitrary methods and constructors also provides the opportunity to solve the hack in the Java interpreter that requires all application base classes to have a static main method that takes an array of String objects as an argument. With the Reflection API, one could construct an interpreter that takes a typed method invocation line, such as:

    jvm myClass(parm1, parm2).someMethod("This is the base method to start");

In my fictitious example, the command jvm takes as its argument a method invocation. The command line is parsed, and first the class is loaded, then reflection is used to identify its constructors. A constructor that takes two parameters is located and the method is found. Finally, the parameter types taken by the method are parsed from the command line, and the object is created and invoked without ever having an artificial "starting" method named main. A really creative individual might use this technique as the basis for a command shell written in Java. Certainly something to think about until next month.

Chuck McManis currently is the director of system software at FreeGate Corp., a venture-funded start-up that is exploring opportunities in the Internet marketplace. Before joining FreeGate, Chuck was a member of the Java Group. He joined the Java Group just after the formation of FirstPerson Inc. and was a member of the portable OS group (the group responsible for the OS portion of Java). Later, when FirstPerson was dissolved, he stayed with the group through the development of the alpha and beta versions of the Java platform. He created the first "all Java" home page on the Internet when he did the programming for the Java version of the Sun home page in May 1995. He also developed a cryptographic library for Java and versions of the Java class loader that could screen classes based on digital signatures. Before joining FirstPerson, Chuck worked in the operating systems area of SunSoft, developing networking applications, where he did the initial design of NIS+. Check out his home page.

Learn more about this topic

  • "Take a look inside Java classes"
    Learn to deduce properties of a Java class from inside a Java program. http://www.javaworld.com/javaworld/jw-08-1997/jw-08-indepth.html
  • "Build an interpreter in Java -- Implement the execution engine"
    Here's how to take the interpreter classes and run with them. http://www.javaworld.com/javaworld/jw-07-1997/jw-07-indepth.html
  • "How to build an interpreter in Java, Part 2The structure"
    The trick to assembling the foundation classes for a simple interpreter. http://www.javaworld.com/javaworld/jw-06-1997/jw-06-indepth.html
  • "How to build an interpreter in Java, Part 1The BASICs"
    For complex applications requiring a scripting language, Java can be used to implement the interpreter, adding scripting abilities to any Java app. http://www.javaworld.com/javaworld/jw-05-1997/jw-05-indepth.html
  • "Lexical analysis, Part 2Build an application"
    How to use the StreamTokenizer object to implement an interactive calculator. http://www.javaworld.com/javaworld/jw-02-1997/jw-02-indepth.html
  • "Lexical analysis and JavaPart 1"
    Learn how to convert human-readable text into machine-readable data using the StringTokenizer and StreamTokenizer classes. http://www.javaworld.com/javaworld/jw-01-1997/jw-01-indepth.html
  • "Code reuse and object-oriented systems"
    Use a helper class to enforce dynamic behavior. http://www.javaworld.com/javaworld/jw-12-1996/jw-12-indepth.html
  • "Container support for objects in Java 1.0.2"
    Organizing objects is easy when you put them into containers. This article walks you through the design and implementation of a container. http://www.javaworld.com/javaworld/jw-11-1996/jw-11-indepth.html
  • "The basics of Java class loaders"
    The fundamentals of this key component of the Java architecture. http://www.javaworld.com/javaworld/jw-10-1996/jw-10-indepth.html
  • "Not using garbage collection"
    Minimize heap thrashing in your Java programs. http://www.javaworld.com/javaworld/jw-09-1996/jw-09-indepth.html
  • "Threads and applets and visual controls"
    This final part of the series explores reading multiple data channels. http://www.javaworld.com/javaworld/jw-07-1996/jw-07-mcmanis.html
  • "Using communication channels in applets, Part 3"
    Develop Visual Basic-style techniques to applet design -- and convert temperatures in the process. http://www.javaworld.com/javaworld/jw-06-1996/jw-06-mcmanis.html
  • "Synchronizing threads in Java, Part II"
    Learn how to write a data channel class, and then create a simple example application that illustrates a real-world implementation of the class. http://www.javaworld.com/javaworld/jw-05-1996/jw-05-mcmanis.html
  • "Synchronizing threads in Java"
    Former Java team developer Chuck McManis walks you through a simple example illustrating how to synchronize threads to assure reliable and predictable applet behavior. http://www.javaworld.com/javaworld/jw-04-1996/jw-04-synch.html

Related:
1 2 Page 2
Page 2 of 2