Reflection

   

Core Java™ 2: Volume I - Fundamentals
By Cay S. Horstmann, Gary Cornell
Table of Contents
Chapter 5.  Inheritance


The class Class gives you a very rich and elaborate toolset to write programs that manipulate Java code dynamically. This feature is heavily used in JavaBeans, the component architecture for Java (see Volume 2 for more on JavaBeans). Using reflection, Java is able to support tools like the ones users of Visual Basic have grown accustomed to. In particular, when new classes are added at design or run time, rapid application development tools that are JavaBeans-enabled need to be able to inquire about the capabilities of the classes that were added. (This is equivalent to the process that occurs when you add controls in Visual Basic to the toolbox.)

A program that can analyze the capabilities of classes is called reflective. The package that brings this functionality to Java is therefore called, naturally enough, java.lang.reflect. The reflection mechanism is extremely powerful. As the next four sections show, you can use it to:

  • Analyze the capabilities of classes at run time;

  • Inspect objects at run time, for example, to write a single toString method that works for all classes;

  • Implement generic array manipulation code;

  • Take advantage of Method objects that work just like function pointers in languages such as C++.

Reflection is a powerful and complex mechanism; however, it is of interest mainly to tool builders, not application programmers. If you are interested in programming applications rather than tools for other Java programmers, you can safely skip the remainder of this chapter and return to it at a later time.

Using Reflection to Analyze the Capabilities of Classes

Here is a brief overview of the most important parts of the reflection mechanism for letting you examine the structure of a class.

The three classes Field, Method, and Constructor in the java.lang.reflect package describe the fields, methods, and constructors of a class, respectively. All three classes have a method called getName that returns the name of the item. The Field class has a method getType that returns an object, again of type Class, that describes the field type. The Method and Constructor classes have methods to report the return type and the types of the parameters used for these methods. All three of these classes also have a method called getModifiers that returns an integer, with various bits turned on and off, that describes the modifiers used, such as public and static. You can then use the static methods in the Modifier class in the java.lang.reflect package to analyze the integer that getModifiers returns. For example, there are methods like isPublic, isPrivate, or isFinal in the Modifier class that you could use to tell whether a method or constructor was public, private, or final. All you have to do is have the appropriate method in the Modifier class work on the integer that getModifiers returns. You can also use the Modifier.toString method to print the modifiers.

The getFields, getMethods, and getConstructors methods of the Class class return arrays of the public fields, operations, and constructors that the class supports. This includes public members of superclasses. ThegetDeclaredFields, getDeclaredMethods, and getDeclaredConstructors methods of the Class class return arrays consisting of all fields, operations, and constructors that are declared in the class. This includes private and protected members, but not members of superclasses.

Example 5-5 shows you how to print out all information about a class. The program prompts you for the name of a class and then writes out the signatures of all methods and constructors as well as the names of all data fields of a class. For example, if you enter

 java.lang.Double 

then the program prints:

 class java.lang.Double extends java.lang.Number {    public java.lang.Double(java.lang.String);    public java.lang.Double(double);    public int hashCode();    public int compareTo(java.lang.Object);    public int compareTo(java.lang.Double);    public boolean equals(java.lang.Object);    public java.lang.String toString();    public static java.lang.String toString(double);    public static java.lang.Double valueOf(java.lang.String);    public static boolean isNaN(double);    public boolean isNaN();    public static boolean isInfinite(double);    public boolean isInfinite();    public byte byteValue();    public short shortValue();    public int intValue();    public long longValue();    public float floatValue();    public double doubleValue();    public static double parseDouble(java.lang.String);    public static native long doubleToLongBits(double);    public static native long doubleToRawLongBits(double);    public static native double longBitsToDouble(long);    public static final double POSITIVE_INFINITY;    public static final double NEGATIVE_INFINITY;    public static final double NaN;    public static final double MAX_VALUE;    public static final double MIN_VALUE;    public static final java.lang.Class TYPE;    private double value;    private static final long serialVersionUID; } 

What is remarkable about this program is that it can analyze any class that the Java interpreter can load, not just the classes that were available when the program was compiled. We will use this program in the next chapter to peek inside the inner classes that the Java compiler generates automatically.

Example 5-5 ReflectionTest.java
   1. import java.lang.reflect.*;   2. import javax.swing.*;   3.   4. public class ReflectionTest   5. {   6.    public static void main(String[] args)   7.    {   8.       // read class name from command line args or user input   9.       String name;  10.       if (args.length > 0)  11.          name = args[0];  12.       else  13.          name = JOptionPane.showInputDialog  14.             ("Class name (e.g. java.util.Date): ");  15.  16.       try  17.       {  18.          // print class name and superclass name (if != Object)  19.          Class cl = Class.forName(name);  20.          Class supercl = cl.getSuperclass();  21.          System.out.print("class " + name);  22.          if (supercl != null && supercl != Object.class)  23.             System.out.print(" extends " + supercl.getName());  24.  25.          System.out.print("\n{\n");  26.          printConstructors(cl);  27.          System.out.println();  28.          printMethods(cl);  29.          System.out.println();  30.          printFields(cl);  31.          System.out.println("}");  32.       }  33.       catch(ClassNotFoundException e) { e.printStackTrace(); }  34.       System.exit(0);  35.    }  36.  37.    /**  38.       Prints all constructors of a class  39.       @param cl a class  40.     */  41.    public static void printConstructors(Class cl)  42.    {  43.       Constructor[] constructors = cl.getDeclaredConstructors();  44.  45.       for (int i = 0; i < constructors.length; i++)  46.       {  47.          Constructor c = constructors[i];  48.          String name = c.getName();  49.          System.out.print(Modifier.toString(c.getModifiers()));  50.          System.out.print("   " + name + "(");  51.  52.          // print parameter types  53.          Class[] paramTypes = c.getParameterTypes();  54.          for (int j = 0; j < paramTypes.length; j++)  55.          {  56.             if (j > 0) System.out.print(", ");  57.             System.out.print(paramTypes[j].getName());  58.          }  59.          System.out.println(");");  60.       }  61.    }  62.  63.   /**  64.       Prints all methods of a class  65.       @param cl a class  66.     */  67.    public static void printMethods(Class cl)  68.    {  69.       Method[] methods = cl.getDeclaredMethods();  70.  71.       for (int i = 0; i < methods.length; i++)  72.       {  73.          Method m = methods[i];  74.          Class retType = m.getReturnType();  75.          String name = m.getName();  76.  77.          // print modifiers, return type and method name  78.          System.out.print(Modifier.toString(m.getModifiers()));  79.          System.out.print("   " + retType.getName() + " " + name  80.             + "(");  81.  82.          // print parameter types  83.          Class[] paramTypes = m.getParameterTypes();  84.          for (int j = 0; j < paramTypes.length; j++)  85.          {  86.             if (j > 0) System.out.print(", ");  87.             System.out.print(paramTypes[j].getName());  88.          }  89.          System.out.println(");");  90.       }  91.    }  92.  93.    /**  94.       Prints all fields of a class  95.       @param cl a class  96.     */  97.    public static void printFields(Class cl)  98.    {  99.       Field[] fields = cl.getDeclaredFields(); 100. 101.       for (int i = 0; i < fields.length; i++) 102.       { 103.          Field f = fields[i]; 104.          Class type = f.getType(); 105.          String name = f.getName(); 106.          System.out.print(Modifier.toString(f.getModifiers())); 107.          System.out.println("   " + type.getName() + " " + name 108.             + ";"); 109.       } 110.    } 111. } 

java.lang.Class 1.0

graphics/api_icon.gif
  • Field[] getFields() 1.1

  • Field[] getDeclaredFields() 1.1

    The getFields method returns an array containing Field objects for the public fields of this class or its superclasses. The getDeclaredField method returns an array of Field objects for all fields of this class. The methods return an array of length 0 if there are no such fields, or if the Class object represents a primitive or array type.

  • Method[] getMethods() 1.1

  • Method[] getDeclaredMethods() 1.1

    return an array containing Method objects: getMethods returns public methods and includes inherited methods, getDeclaredMethods returns all methods of this class or interface but does not include inherited methods.

  • Constructor[] getConstructors() 1.1

  • Constructor[] getDeclaredConstructors() 1.1

    return an array containing Constructor objects that give you all the public constructors (for getConstructors) or all constructors (for getDeclaredConstructors) of the class represented by this Class object.

java.lang.reflect.Field 1.1

graphics/api_icon.gif

java.lang.reflect.Method 1.1

graphics/api_icon.gif

java.lang.reflect.Constructor 1.1

graphics/api_icon.gif
  • Class getDeclaringClass()

    returns the Class object for the class that defines this constructor, method, or field.

  • Class[] getExceptionTypes() (in Constructor and Method classes)

    returns an array of Class objects that represent the types of the exceptions thrown by the method.

  • int getModifiers()

    returns an integer that describes the modifiers of this constructor, method, or field. Use the methods in the Modifier class to analyze the return value.

  • String getName()

    returns a string that is the name of the constructor, method, or field.

  • Class[] getParameterTypes() (in Constructor and Method classes)

    returns an array of Class objects that represent the types of the parameters.

  • Class getReturnType() (in Method classes)

    returns a Class object that represents the return type.

java.lang.reflect.Modifier 1.1

graphics/api_icon.gif
  • static String toString(int modifiers)

    returns a string with the modifiers that correspond to the bits set in modifiers.

  • static boolean isAbstract(int modifiers)

  • static boolean isFinal(int modifiers)

  • static boolean isInterface(int modifiers)

  • static boolean isNative(int modifiers)

  • static boolean isPrivate(int modifiers)

  • static boolean isProtected(int modifiers)

  • static boolean isPublic(int modifiers)

  • static boolean isStatic(int modifiers)

  • static boolean isStrict(int modifiers)

  • static boolean isSynchronized(int modifiers)

  • static boolean isVolatile(int modifiers)

    These methods test the bit in the modifiers value that corresponds to the modifier in the method name.

Using Reflection to Analyze Objects at Run Time

In the preceding section, we saw how we can find out the names and types of the data fields of any object:

  • Get the corresponding Class object.

  • Call getDeclaredFields on the Class object.

In this section, we go one step further and actually look at the contents of the data fields. Of course, it is easy to look at the contents of a specific field of an object whose name and type are known when you write a program. But reflection lets you look at fields of objects that were not known at compile time.

The key method to achieve this is the get method in the Field class. If f is an object of type Field (for example, one obtained from getDeclaredFields) and obj is an object of the class of which f is a field, then f.get(obj) returns an object whose value is the current value of the field of obj. This is all a bit abstract, so let's run through an example.

 Employee harry = new Employee("Harry Hacker", 35000,    10, 1, 1989); Class cl = harry.getClass();    // the class object representing Employee Field f = cl.getDeclaredField("name");    // the name field of the Employee class Object v = f.get(harry);    // the value of the name field of the harry object    // i.e. the String object "Harry Hacker" 

Actually, there is a problem with this code. Since the name field is a private field, the get method will throw an IllegalAccessException. You can only use the get method to get the values of accessible fields. The security mechanism of Java lets you find out what fields any object has, but it won't let you read the values of those fields unless you have access permission.

The default behavior of the reflection mechanism is to respect Java access control. However, if a Java program is not controlled by a security manager that disallows it, it is possible to override access control. To do this, invoke the setAccessible method on a Field, Method, or Constructor object, for example:

 f.setAccessible(true);    // now OK to call f.get(harry); 

The setAccessible method is a method of the AccessibleObject class, the common superclass of the Field, Method, and Constructor. This feature is provided for debuggers, persistent storage, and similar mechanisms. We will use it for a generic toString method later in this section.

There is another issue with the get method that we need to deal with. The name field is a String, and so it is not a problem to return the value as an Object. But suppose we want to look at the salary field. That is a double, and in Java, number types are not objects. To handle this, you can either use the getDouble method of the Field class, or you can call get, where the reflection mechanism automatically wraps the field value into the appropriate wrapper class, in this case, Double.

Of course, you can also set the values that you can get. The call f.set(obj, value) sets the field represented by f of the object obj to the new value.

Example 5-6 shows how to write a generic toString method that works for any class. It uses getDeclaredFields to obtain all data fields. It then uses the setAccessible convenience method to make all fields accessible. For each field, it obtains the name and the value. Each value is turned into a string by recursively invoking toString.

 class ObjectAnalyzer {   public String toString(Object obj)   {       Class cl = obj.getClass();       . . .       String r = cl.getName();       // inspect the fields of this class and all superclasses       do       {          r += "[";          Field[] fields = cl.getDeclaredFields();          AccessibleObject.setAccessible(fields, true);          // get the names and values of all fields          for (int i = 0; i < fields.length; i++)          {             Field f = fields[i];             if (!Modifier.isStatic(f.getModifiers()))             {                if (!r.endsWith("[")) r += ","                r += f.getName() + "=";                try                {                   Object val = f.get(obj);                   r += toString(val);                }                catch (Exception e) { e.printStackTrace(); }             }          }          r += "]";          cl = cl.getSuperclass();       }       while (cl != Object.class);       return r;    }    . . . } 

The complete code in Example 5-6 needs to address a couple of complexities. Cycles of references could cause an infinite recursion. Therefore, the ObjectAnalyzer keeps track of objects that were already visited. Also, to peek inside arrays, a different approach is needed. You'll learn about the details in the next section.

You can use this toString method to peek inside any object. For example, the call

 Object obj = NumberFormat.getCurrencyInstance(); System.out.println(new ObjectAnalyzer().toString(obj)) 

yields the rather impressive printout:

[View full width]

java.text.DecimalFormat[digitList=java.text.DigitList[decimalAt=0,count=0,digits=byte[]{0, graphics/ccc.gif0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}],positivePrefix=$,positiveSuffix=,negativePrefix=($, graphics/ccc.gifnegativeSuffix=),posPrefixPattern='?,posSuffixPattern=...,negPrefixPattern=('?, graphics/ccc.gifnegSuffixPattern=...,multiplier=1,groupingSize=3,decimalSeparatorAlwaysShown=false, graphics/ccc.gifisCurrencyFormat=true,symbols=java.text.DecimalFormatSymbols[zeroDigit=0, graphics/ccc.gifgroupingSeparator=,,decimalSeparator=.,perMill=?,percent=%,digit=#,patternSeparator=;, graphics/ccc.gifinfinity=?,NaN=?,minusSign=-,currencySymbol=...,intlCurrencySymbol=USD, graphics/ccc.gifmonetarySeparator=.,exponential=E,locale=java.util.Locale[language=en,country=US,variant=. graphics/ccc.gif..,hashcode=1591],currency=java.util.Currency[currencyCode=...,defaultFractionDigits=2], graphics/ccc.gifserialVersionOnStream=2],useExponentialNotation=false,positivePrefixFieldPositions=null, graphics/ccc.gifpositiveSuffixFieldPositions=null,negativePrefixFieldPositions=null, graphics/ccc.gifnegativeSuffixFieldPositions=null,minExponentDigits=0, graphics/ccc.gifserialVersionOnStream=2][groupingUsed=true, maxIntegerDigits=40,minIntegerDigits=1, graphics/ccc.gifmaxFractionDigits=3,minFraction Digits=0,parseIntegerOnly=false,maximumIntegerDigits=309, graphics/ccc.gifminimumInteger Digits=1,maximumFractionDigits=2,minimumFractionDigits=2,serialVersion graphics/ccc.gifOnStream=1][]

You can even use this generic toString method to implement the toString methods of your own classes, like this:

 public String toString() {    return new ObjectAnalyzer().toString(this); } 

This is a hassle-free method for supplying a toString method that you may find useful, especially for debugging.

Example 5-6 ObjectAnalyzerTest.java
  1. import java.lang.reflect.*;  2. import java.util.*;  3. import java.text.*;  4.  5. public class ObjectAnalyzerTest  6. {  7.     public static void main(String[] args)  8.     {  9.        Object obj = NumberFormat.getCurrencyInstance(); 10.        System.out.println(new ObjectAnalyzer().toString(obj)); 11.     } 12. } 13. 14. class ObjectAnalyzer 15. { 16.     /** 17.        Converts an object to a string representation that lists 18.        all fields. 19.        @param obj an object 20.        @return a string with the object's class name and all 21.        field names and values 22.     */ 23.     public String toString(Object obj) 24.     { 25.        if (obj == null) return "null"; 26.        if (visited.contains(obj)) return "..."; 27.        visited.add(obj); 28.        Class cl = obj.getClass(); 29.        if (cl == String.class) return (String)obj; 30.        if (cl.isArray()) 31.        { 32.           String r = cl.getComponentType() + "[]{"; 33.           for (int i = 0; i < Array.getLength(obj); i++) 34.           { 35.              if (i > 0) r += ","; 36.              Object val = Array.get(obj, i); 37.              if (cl.getComponentType().isPrimitive()) r += val; 38.              else r += toString(val); 39.           } 40.           return r + "}"; 41.        } 42. 43.        String r = cl.getName(); 44.        // inspect the fields of this class and all superclasses 45.        do 46.        { 47.           r += "["; 48.           Field[] fields = cl.getDeclaredFields(); 49.           AccessibleObject.setAccessible(fields, true); 50.           // get the names and values of all fields 51.           for (int i = 0; i < fields.length; i++) 52.           { 53.              Field f = fields[i]; 54.              if (!Modifier.isStatic(f.getModifiers())) 55.              { 56.                 if (!r.endsWith("[")) r += ","; 57.                 r += f.getName() + "="; 58.                 try 59.                 { 60.                    Class t = f.getType(); 61.                    Object val = f.get(obj); 62.                    if (t.isPrimitive()) r += val; 63.                    else r += toString(val); 64.                 } 65.                 catch (Exception e) { e.printStackTrace(); } 66.              } 67.           } 68.           r += "]"; 69.           cl = cl.getSuperclass(); 70.        } 71.        while (cl != null); 72. 73.        return r; 74.     } 75. 76.     private ArrayList visited = new ArrayList(); 77. } 

java.lang.reflect.AccessibleObject 1.2

graphics/api_icon.gif
  • void setAccessible(boolean flag)

    sets the accessibility flag for this reflection object. A value of true indicates that Java language access checking is suppressed, and that the private properties of the object can be queried and set.

  • boolean isAccessible()

    gets the value of the accessibility flag for this reflection object.

  • static void setAccessible(AccessibleObject[] array, boolean flag)

    is a convenience method to set the accessibility flag for an array of objects.

Using Reflection to Write Generic Array Code

The Array class in the java.lang.reflect package allows you to create arrays dynamically. For example, when you use this feature with the arrayCopy method from Chapter 3, you can dynamically expand an existing array while preserving the current contents.

The problem we want to solve is pretty typical. Suppose you have an array of some type that is full and you want to grow it. And suppose you are sick of writing the grow-and-copy code by hand. You want to write a generic method to grow an array.

 Employee[] a = new Employee[100]; . . . // array is full a = (Employee[])arrayGrow(a); 

How can we write such a generic method? It helps that an Employee[] array can be converted to an Object[] array. That sounds promising. Here is a first attempt to write a generic method. We simply grow the array by 10% + 10 elements (since the 10% growth is not substantial enough for small arrays).

 static Object[] arrayGrow(Object[] a) // not useful {    int newLength = a.length * 11 / 10 + 10;    Object[] newArray = new Object[newLength];    System.arraycopy(a, 0, newArray, 0, a.length);    return newArray; } 

However, there is a problem with actually using the resulting array. The type of array that this code returns is an array of objects (Object[]) because we created the array using the line of code:

 new Object[newLength] 

An array of objects cannot be cast to an array of employees (Employee[]). Java would generate a ClassCast exception at run time. The point is, as we mentioned earlier, that a Java array remembers the type of its entries, that is, the element type used in the new expression that created it. It is legal to cast an Employee[] temporarily to an Object[] array and then cast it back, but an array that started its life as an Object[] array can never be cast into an Employee[] array. To write this kind of generic array code, we need to be able to make a new array of the same type as the original array. For this, we need the methods of the Array class in the java.lang.reflect package. The key is the static newInstance method of the Array class that constructs a new array. You must supply the type for the entries and the desired length as parameters to this method.

 Object newArray = Array.newInstance(componentType, newLength); 

To actually carry this out, we need to get the length and component type of the new array.

The length is obtained by calling Array.getLength(a). The static getLength method of the Array class returns the length of any array. To get the component type of the new array:

  1. First, get the class object of a.

  2. Confirm that it is indeed an array.

  3. Use the getComponentType method of the Class class (which is defined only for class objects that represent arrays) to find the right type for the array.

Why is getLength a method of Array but getComponentType a method of Class? We don't know the distribution of the reflection methods seems a bit ad hoc at times.

Here's the code:

 static Object arrayGrow(Object a) // useful {    Class cl = a.getClass();    if (!cl.isArray()) return null;    Class componentType = cl.getComponentType();    int length = Array.getLength(a);    int newLength = length * 11 / 10 + 10;     Object newArray = Array.newInstance(componentType,       newLength);    System.arraycopy(a, 0, newArray, 0, length);    return newArray; } 

Note that this arrayGrow method can be used to grow arrays of any type, not just arrays of objects.

 int[] ia = { 1, 2, 3, 4 }; ia = (int[])arrayGrow(ia); 

To make this possible, the parameter of arrayGrow is declared to be of type Object, not an array of objects (Object[]). The integer array type int[] can be converted to an Object, but not to an array of objects!

Example 5-7 shows both array grow methods in action. Note that the cast of the return value of badArrayGrow will throw an exception.

Example 5-7 ArrayGrowTest.java
  1. import java.lang.reflect.*;  2. import java.util.*;  3.  4. public class ArrayGrowTest  5. {  6.    public static void main(String[] args)  7.    {  8.       int[] a = { 1, 2, 3 };  9.       a = (int[])goodArrayGrow(a); 10.       arrayPrint(a); 11. 12.       String[] b = { "Tom", "Dick", "Harry" }; 13.       b = (String[])goodArrayGrow(b); 14.       arrayPrint(b); 15. 16.       System.out.println 17.          ("The following call will generate an exception."); 18.       b = (String[])badArrayGrow(b); 19.    } 20. 21.    /** 22.       This method attempts to grow an array by allocating a 23.       new array and copying all elements. 24.       @param a the array to grow 25.       @return a larger array that contains all elements of a. 26.       However, the returned array has type Object[], not 27.       the same type as a 28.    */ 29.    static Object[] badArrayGrow(Object[] a) 30.    { 31.       int newLength = a.length * 11 / 10 + 10; 32.       Object[] newArray = new Object[newLength]; 33.       System.arraycopy(a, 0, newArray, 0, a.length); 34.       return newArray; 35.    } 36. 37.    /** 38.       This method grows an array by allocating a 39.       new array of the same type and copying all elements. 40.       @param a the array to grow. This can be an object array 41.       or a fundamental type array 42.       @return a larger array that contains all elements of a. 43. 44.    */ 45.    static Object goodArrayGrow(Object a) 46.    { 47.       Class cl = a.getClass(); 48.       if (!cl.isArray()) return null; 49.       Class componentType = cl.getComponentType(); 50.       int length = Array.getLength(a); 51.       int newLength = length * 11 / 10 + 10; 52. 53.       Object newArray = Array.newInstance(componentType, 54.          newLength); 55.       System.arraycopy(a, 0, newArray, 0, length); 56.       return newArray; 57.    } 58. 59.    /** 60.       A convenience method to print all elements in an array 61.       @param a the array to print. can be an object array 62.       or a fundamental type array 63.    */ 64.    static void arrayPrint(Object a) 65.    { 66.       Class cl = a.getClass(); 67.       if (!cl.isArray()) return; 68.       Class componentType = cl.getComponentType(); 69.       int length = Array.getLength(a); 70.       System.out.print(componentType.getName() 71.          + "[" + length + "] = { "); 72.       for (int i = 0; i < Array.getLength(a); i++) 73.          System.out.print(Array.get(a, i)+ " "); 74.       System.out.println("}"); 75.    } 76. } 

java.lang.reflect.Array 1.1

graphics/api_icon.gif
  • public static Object get(Object array, int index)

  • public static xxx getXxx(Object array, int index)

    (xxx is one of the primitive types boolean, byte, char, double, float, int, long, short) These methods return the value of the given array that is stored at the given index.

  • public static void set(Object array, int index, Object newValue)

  • public static setXxx(Object array, int index, xxx newValue)

    (xxx is one of the primitive types boolean, byte, char, double, float, int, long, short) These methods store a new value into the given array at the given index.

  • public static int getLength(Object array)

    returns the length of the given array.

  • Object newInstance(Class componentType, int length)

  • Object newInstance(Class componentType, int[] lengths)

    return a new array of the given component type with the given dimensions.

Method Pointers!

On the surface, Java does not have method pointers ways of giving the location of a method to another method so that the second method can invoke it later. In fact, the designers of Java have said that method pointers are dangerous and error-prone and that Java interfaces (discussed in the next chapter) are a superior solution. However, it turns out that Java now does have method pointers, as a (perhaps accidental) by-product of the reflection package.

graphics/notes_icon.gif

Among the nonstandard language extensions that Microsoft added to its Java derivative J++ (and its successor, C#) is another method pointer type that is different from the Method class that we discuss in this section. However, as you will see in Chapter 6, inner classes are a more useful and general mechanism.

To see method pointers at work, recall that you can inspect a field of an object with the get method of the Field class. Similarly, the Method class has an invoke method that lets you call the method that is wrapped in the current Method object. The signature for the invoke method is:

 Object invoke(Object obj, Object[] args) 

The first parameter is the implicit parameter, and the array of objects provides the explicit parameters. For a static method, the first parameter is ignored you can set it to null. If the method has no explicit parameters, you can pass null or an array of length 0 for the args parameter. For example, if m1 represents the getName method of the Employee class, the following code shows how you can call it:

 String n = (String)m1.invoke(harry, null); 

As with the get and set methods of the Field type, there's a problem if the parameter or return type is not a class but a basic type. You must wrap any of the basic types into their corresponding wrappers before inserting them into the args array. Conversely, the invoke method will return the wrapped type and not the basic type. For example, suppose that m2 represents the raiseSalary method of the Employee class. Then, you need to wrap the double parameter into a Double object.

 Object[] args = { new Double(5.5) }; m2.invoke(harry, args); 

How do you obtain a Method object? You can, of course, call getDeclaredMethods and search through the returned array of Method objects until you find the method that you want. Or, you can call the getMethod method of the Class class. This is similar to the getField method that takes a string with the field name and returns a Field object. However, there may be several methods with the same name, so you need to be careful that you get the right one. For that reason, you must also supply an array that gives the correct parameter types. For example, here is how you can get method pointers to the getName and raiseSalary methods of the Employee class.

 Method m1 = Employee.class.getMethod("getName", null); Method m2 = Employee.class.getMethod("raiseSalary",    new Class[] { double.class } ); 

The second parameter of the getMethod method is an array of Class objects. Since the raiseSalary method has one parameter of type double, we must supply an array with a single element, double.class. It is usually easiest to make that array on the fly, as we did in the example above. The expression

 new Class[] { double.class } 

denotes an array of Class objects, filled with one element, namely, the class object double.class.

Now that you have seen the syntax of Method objects, let's put them to work. Example 5-8 is a program that prints a table of values for a mathematical function such as Math.sqrt or Math.sin. The printout looks like this:

 public static native double java.lang.Math.sqrt(double)       1.0000 |      1.0000       2.0000 |      1.4142       3.0000 |      1.7321       4.0000 |      2.0000       5.0000 |      2.2361       6.0000 |      2.4495       7.0000 |      2.6458       8.0000 |      2.8284       9.0000 |      3.0000      10.0000 |      3.1623 

The code for printing a table is, of course, independent of the actual function that is being tabulated.

 double dx = (to - from) / (n - 1); for (double x = from; x <= to; x += dx) {    double y = f(x);       // where f is the function to be tabulated       // not the actual syntax--see below    System.out.println(x + " | " + y); } 

We want to write a generic printTable method that can tabulate any function. We will pass the function as a parameter of type Method.

 static void printTable(double from, double to, int n, Method f) 

Of course, f is an object and not a function, so we cannot simply write f(x) to evaluate it. Instead, we must supply x in the parameter array (suitably wrapped as a Double), use the invoke method, and unwrap the return value.

 Object[] args = { new Double(x) }; Double d = (Double)f.invoke(null, args); double y = d.doubleValue(); 

The first parameter of invoke is null because we are calling a static method.

Here is a sample call to printTable that tabulates the square root function.

 printTable(1, 10, 10,    java.lang.Math.class.getMethod("sqrt",    new Class[] { double.class })); 

The hardest part is to get the method object. Here, we get the method of the java.lang.Math class that has the name sqrt and whose parameter list contains just one type, double.

Example 5-8 shows the complete code of the printTable method and a couple of test runs.

Example 5-8 MethodPointerTest.java
  1. import java.lang.reflect.*;  2. import java.text.*;  3.  4. public class MethodPointerTest  5. {  6.    public static void main(String[] args) throws Exception  7.    {  8.       // get method pointers to the square and sqrt methods  9.       Method square = MethodPointerTest.class.getMethod("square", 10.          new Class[] { double.class }); 11.       Method sqrt = java.lang.Math.class.getMethod("sqrt", 12.          new Class[] { double.class }); 13. 14.       // print tables of x- and y-values 15. 16.       printTable(1, 10, 10, square); 17.       printTable(1, 10, 10, sqrt); 18.    } 19. 20.    /** 21.       Returns the square of a number 22.       @param x a number 23.       @return x squared 24.    */ 25.    public static double square(double x) 26.    { 27.       return x * x; 28.    } 29. 30.    /** 31.       Prints a table with x- and y-values for a method 32.       @param from the lower bound for the x-values 33.       @param to the upper bound for the x-values 34.       @param n the number of rows in the table 35.       @param f a method with a double parameter and double 36.          return value 37.    */ 38.    public static void printTable(double from, double to, 39.       int n, Method f) 40.   { 41.       // print out the method as table header 42.       System.out.println(f); 43. 44.       // construct formatter to print with 4 digits precision 45. 46.       NumberFormat formatter = NumberFormat.getNumberInstance(); 47.       formatter.setMinimumFractionDigits(4); 48.       formatter.setMaximumFractionDigits(4); 49.       double dx = (to - from) / (n - 1); 50. 51.       for (double x = from; x <= to; x += dx) 52.       { 53.          // print x-value 54.          String output = formatter.format(x); 55.          // pad with spaces to field width of 10 56.          for (int i = 10 - output.length(); i > 0; i--) 57.             System.out.print(' '); 58.          System.out.print(output + " |"); 59. 60.          try 61.          { 62.             // invoke method and print y-value 63.             Object[] args = { new Double(x) }; 64.             Double d = (Double)f.invoke(null, args); 65.             double y = d.doubleValue(); 66. 67.             output = formatter.format(y); 68.             // pad with spaces to field width of 10 69.             for (int i = 10 - output.length(); i > 0; i--) 70.                System.out.print(' '); 71. 72.             System.out.println(output); 73.          } 74.          catch (Exception e) {  e.printStackTrace(); } 75.       } 76.    } 77. } 

As this example shows clearly, you can do anything with Method objects that you can do with function pointers in C (or delegates in C#). Just as in C, this style of programming is usually quite inconvenient and always error-prone. What happens if you invoke a method with the wrong parameters? The invoke method throws an exception.

Also, the parameters and return values of invoke are necessarily of type Object. That means you must cast back and forth a lot. As a result, the compiler is deprived of the chance to check your code. Therefore, errors surface only during testing, when they are more tedious to find and fix. Moreover, code that uses reflection to get at method pointers is significantly slower than simply calling methods directly.

For that reason, we suggest that you use Method objects in your own programs only when absolutely necessary. Using interfaces and inner classes (the subject of the next chapter) is almost always a better idea. In particular, we echo the developers of Java and suggest not using Method objects for callback functions. Using interfaces for the callbacks (see the next chapter as well) leads to code that runs faster and is a lot more maintainable.

java.lang.reflect.Method 1.1

graphics/api_icon.gif
  • public Object invoke(Object implicitParameter, Object[] explicitParameters)

    invokes the method described by this object, passing the given parameters, and returning the value that the method returns. For static methods, pass null as the implicit parameter. Pass primitive type values by using wrappers. Primitive type return values must be unwrapped.


       
    Top
     



    Core Java 2(c) Volume I - Fundamentals
    Building on Your AIX Investment: Moving Forward with IBM eServer pSeries in an On Demand World (MaxFacts Guidebook series)
    ISBN: 193164408X
    EAN: 2147483647
    Year: 2003
    Pages: 110
    Authors: Jim Hoskins

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net