All Java classes are subclasses of a pre-defined base class called Object. It doesn't matter if a programmer declares this or not. All Java classes are automatically subclasses of the Object base class. (Except for the Object class itself, obviously.):
public class Object {
protected Object clone() { ... }
public boolean equals(Object obj) { ...
}
public String toString() { ... }
public void notify() { ... }
public void wait() { ... }
public int hashCode() { ... }
public Class getClass() { ... }
// etc.
}
This last feature is interesting. What sort of answer do we expect when we ask an object what class it instantiates? We expect a class, of course. But only objects, not classes, exist at runtime. How can an object return a class in response to this query? The solution is that instead of returning a class, it returns an object representing a class. At first this notion sounds strange. How can objects represent classes? But of course objects represent all sorts of things: cars, houses, boats, people, cats, planets. Why not classes?
The class of all objects that represent classes is defined in the java.lang package. Naturally, the name of this class is Class. It includes methods for discovering the name of the class (getName), the super class (getSuperClass), the methods (getMethods), and the attributes (getFields):
class Class {
public static Class forName(String
name) { ... }
public Object newInstance() { ... }
public String getName() { ... }
public Class getSuperClass() { ... }
public Field getField(String name) {
... }
public Field[] getFields() { ... }
public Method getMethod(String name) {
... }
public Method[] getMethods() { ... }
// etc.
}
Of course this implies that there are also objects representing methods and fields. Naturally, these objects belong to the Method and Field classes, respectively, and of course like all Java classes, Class, Method, and Field are subclasses of the Object base class:
To demonstrate reflection in Java, we introduce a hierarchy of classes that represent different types of musical notes:
The Note class declares two public integer fields representing the duration and frequency of a note. It also declares a play() method that simply prints a message in the console window, as well as a static method called main(), where we can place our test code:
public class Note {
public int frequency = 60; // frequency of note in Hz
public int duration = 300; // duration of note in millisecs
public void play() {
System.out.println("Playing a
generic note");
}
public static void main(String[] args)
{
// test code goes here
}
}
HornNote and ViolinNote are subclasses of Note that override the inherited play() method:
class HornNote extends Note {
public void play() {
System.out.println("Playing a
horn note");
}
}
class ViolinNote extends Note {
public void play() {
System.out.println("Playing a
violin note");
}
}
Our test driver, Note.main(), begins by declaring a reference to a note:
Note note;
Unlike C++, no object is created by this declaration. Instead, note is simply a variable capable of holding a reference to any object that instantiates the Note class or any of its subclasses. Java objects are created by the new operator, which creates an object on the heap, then returns a reference to it. The following statement creates a HornNote object, then places a reference to this object in the note variable:
note = new HornNote();
Next, we ask note which class it instantiates, then print the name of this class:
Class c = note.getClass();
System.out.println("class of note = " + c.getName());
Here is the output produced:
class of note = HornNote
Notice that Java wasn't fooled by the fact that note was declared as a Note reference. Instead, it dug deeper, returning the class of the object note currently references. To make this point clearer, we reassign note so that it references a ViolinNote, reassign c so that once again refers to the class of note, then print the class name:
note = new ViolinNote();
c = note.getClass();
System.out.println("now class of note = " + c.getName());
Now here's the output produced:
now class of note = ViolinNote
Again notice that getClass() actually fetches the class of the object, not simply the class of the reference.
Of course we can reassign c to reference any super class of ViolinNote:
c = c.getSuperclass();
System.out.println("base class of note = " + c.getName());
c = c.getSuperclass();
System.out.println("base of base class of note = " + c.getName());
Here is the output produced:
base class of note = Note
base of base class of note = java.lang.Object
In addition to information about super classes, we can also find out about the methods and fields of a class. Assuming c still references an object representing the ViolinNote class, then the following loop prints out the names of all of the ViolinClass methods:
Method methods[] = c.getMethods();
for(int i = 0; i < methods.length; i++)
System.out.println(methods[i].getName());
Here's the output produced:
main
hashCode
wait
wait
wait
getClass
equals
toString
notify
notifyAll
play
Notice that in addition to play(), all methods inherited from the Note and Object super classes are also listed. (The wait() method inherited from the Object super class appears three times because there are actually three different methods with this name.) Of course we could have printed out much more than the names of the methods. For example, we could have printed the parameter lists, the exception lists, and the return types.
The following code prints the names of the ViolinNote fields as well as their current values in the particular ViolinNote object referenced by note:
Field fields[] = c.getFields();
try {
for(int i = 0; i < fields.length;
i++) {
System.out.print(fields[i].getName()
+ " = ");
System.out.println(fields[i].getInt(note));
}
} catch(Exception e) {
// handle e
}
Here is the output produced:
frequency = 60
duration = 300
Non-public fields aren't printed.
Surprisingly, we can ask a Method object to invoke the method it represents. Of course we must provide it with the implicit and explicit arguments. For example, let's create a generic Note object, then call its play() method using reflection:
note = new Note();
c = note.getClass();
Method meth = c.getMethod("play", null);
meth.invoke(note, null);
Here's the output produced:
Playing a generic note
We repeat the experiment using a HornNote:
note = new HornNote();
c = note.getClass();
meth = c.getMethod("play", null);
meth.invoke(note, null);
Here's the output produced:
Playing a horn note
Notice that the HornNote play() method was invoked instead of the Note play() method.
Finally, we repeat the experiment one last time using a ViolinNote:
note = new ViolinNote();
c = note.getClass();
meth = c.getMethod("play", null);
meth.invoke(note, null);
Here's the output produced:
Playing a violin note
Of course it's far more efficient to simply call the play() method directly:
note.play();
Reflection is useful in those situations where we don't know which method we want to call at the time we are writing our program. Instead, this information will only be available at runtime.
Sometimes we don't even know the type of object we want to create until our program is running. We saw examples of this problem when we introduced the Factory Method Pattern in Chapter 3. In Java, we can create an instance of a class, C, from a Class object representing C using the newInstance() method. To make things harder, we will assume nothing is known about C at compile time. This might be the case if we were trying to define a universal instrument class. A universal instrument can imitate all other types of instruments. This is done with a play() method that expects as its input only the name of the type of note to play:
class UniversalInstrument {
public void play(String noteType) {
try {
Class c =
Class.forName(noteType); // find
& load a class
Note note = (Note)
c.newInstance();
note.play();
} catch (Exception e) {
// handle e here
}
}
}
Internally, the play() method first converts the name of the class, noteType, into an object representing the class itself, using the static forName() method. For example, if noteType is the string "HornNote", then forName() searches for a file named HornNote.class (this is the conventional name for the file containing the binary definition of the HornNote class), dynamically loads the file into the Java virtual machine, then creates and returns a Class object representing the HornNote class.
From the Class object, c, the newInstance() method is invoked. This creates an instance of the class represented by c. Of course this is returned as an Object, so in our example we perform an explicit downcast to the Note class, then call the play() method.
After creating a universal instrument, our test driver calls the play() method twice. The first time the string "ViolinNote" is the argument. The second time the string "HornNote" is the argument:
UniversalInstrument inst = new UniversalInstrument();
String noteType;
noteType = "ViolinNote";
inst.play(noteType);
noteType = "HornNote";
inst.play(noteType);
Here's the output produced:
Playing a violin note
Playing a horn note
Of course if we wanted to create and play a HornNote followed by a ViolinNote, why not simply do it directly:
note = new HornNote();
note.play();
note = new ViolinNote();
note.play();
To see why, suppose instead of hardwiring the "ViolinNote" and "HornNote" strings into our test program, we allow the user to specify the strings:
System.out.print("enter a type of note: ");
noteType = MyTools.stdin.readLine();
inst.play(noteType);
We don't know what the user will enter, so we don't know what type of notes to make.
public class UniversalProgram {
public void exec(String className, String methName) {
try {
Class c =
Class.forName(className); // find & load a class
Method meth =
c.getMethod(methName, null);
Object blob = c.newInstance();
meth.invoke(blob, null);
} catch (Exception e) {
// handle e here
}
}
// usage:
public static void main(String[] args) {
UniversalProgram turing = new
UniversalProgram();
String objectType;
objectType = args[0];
turing.exec(objectType, args[1]);
}
}