Chapter 7. Working with Objects and Classes

CONTENTS
  •  7.1 The Object Class
  •  7.2 The Class Class
  •  7.3 Reflection

In the previous two chapters, we came to know Java objects and their interrelationships. We will now climb the scaffolding of the Java class hierarchy to the very top and finish our study of the core language at the summit. In this chapter we'll talk about the Object class itself, which is the "grandmother" of all classes in Java. We'll also describe the even more fundamental Class class (the class named "Class") that represents Java classes in the Java virtual machine. We'll discuss what you can do with these objects in their own right. Finally, this will lead us to a more general topic: the Java Reflection API, which lets a Java program inspect and interact with (possibly unknown) objects on the fly.

7.1 The Object Class

java.lang.Object is the ancestor of all objects; it's the primordial class from which all other classes are ultimately derived. Methods defined in Object are therefore very important because they appear in every instance of every class, throughout all of Java. At last count, there were nine public methods in Object. Five of these are versions of wait() and notify() that are used to synchronize threads on object instances, as we'll discuss in Chapter 8. The remaining four methods are used for basic comparison, conversion, and administration.

Every object has a toString() method that can be called when it's to be represented as a text value. PrintStream objects use toString() to print data, as discussed in Chapter 11. toString() is also used implicitly when an object is referenced in a string concatenation. Here are some examples:

MyObj myObject = new MyObj( );   Answer theAnswer = new Answer( );      System.out.println( myObject );   String s = "The answer is: " + theAnswer ;

To be friendly, a new kind of object should override toString() and implement its own version that provides appropriate printing functionality. Two other methods, equals() and hashCode(), may also require specialization when you create a new class.

7.1.1 Equality and Equivalence

equals() determines whether two objects are equivalent. Precisely what that means for a particular class is something that you'll have to decide for yourself. Two String objects, for example, are considered equivalent if they hold precisely the same characters in the same sequence:

String userName = "Joe";   ...   if ( userName.equals( suspectName ) )      arrest( userName );

Using equals() is not the same as:

if ( userName == suspectName )      // Wrong!

This code line tests whether the two reference variables, userName and suspectName, refer to the same object. It is a test for identity, not equality. Two variables that are identical (point to the same object) will of course test equal, but the converse is not always true.

A class should override the equals() method if it needs to implement its own notion of equality. If you have no need to compare objects of a particular class, you don't necessarily need to override equals().

Watch out for accidentally overloading equals() when you mean to override it. With overloading, the method signatures differ; with overriding, they must be the same. The equals() method signature specifies an Object argument and a boolean return value. You'll probably want to check only objects of the same type for equivalence. But in order to override (not overload) equals(), the method must specify its argument to be an Object.

Here's an example of correctly overriding an equals() method in class Shoes with an equals() method in subclass Sneakers. Using its own method, a Sneakers object can compare itself with any other object:

class Sneakers extends Shoes {       public boolean equals( Object arg ) {           if ( (arg != null) && (arg instanceof Sneakers) ) {               // compare arg with this object to check equivalence               // If comparison is okay...               return true;           }           return false;       }       ...   }

If we specified public boolean equals(Sneakers arg) ... in the Sneakers class, we'd overload the equals() method instead of overriding it. If the other object happens to be assigned to a non-Sneakers variable, the method signature won't match. The result: superclass Shoes's implementation of equals() is called, which may or may not be what you intended.

7.1.2 Hashcodes

The hashCode() method returns an integer that is a hashcode for the object. A hashcode is like a signature or checksum for an object; it's a random-looking identifying number that is usually generated from the contents of the object. The hashcode should always be different for instances of the class that contain different data, but should be the same for instances that compare "equal" with the equals() method. Hashcodes are used in the process of storing objects in a Hashtable or a similar kind of collection. The hashcode helps the Hashtable optimize its storage of objects by serving as an identifier for distributing them into storage evenly and locating them quickly later.

The default implementation of hashCode() in Object assigns each object instance a unique number. If you don't override this method when you create a subclass, each instance of your class will have a unique hashcode. This is sufficient for some objects. However, if your classes have a notion of equivalent objects (if you have overridden equals()), and you want equal objects to serve as equivalent keys in a Hashtable, you should override hashCode() so that your equivalent objects generate the same hashcode value.

7.1.3 Cloning Objects

Objects can use the clone() method of the Object class to make copies of themselves. A copied object is a new object instance, separate from the original. It may or may not contain exactly the same state (the same instance variable values) as the original; that is controlled by the object being copied. Just as important, the decision as to whether the object allows itself to be cloned at all is up to the object.

The Java Object class provides the mechanism to make a simple copy of an object including all of its state a bitwise copy. But by default this capability is turned off. (We'll hit upon why in a moment.) To make itself cloneable, an object must implement the java.lang.Cloneable interface. This is a flag interface indicating to Java that the object wants to cooperate in being cloned (the interface does not actually contain any methods). If the object isn't cloneable, the clone() method throws a CloneNotSupportedException.

clone() is a protected method, so by default it can be called only by an object on itself, an object in the same package, or another object of the same type or a subtype. If we want to make an object cloneable by everyone, we have to override its clone() method and make it public.

Here is a simple, cloneable class Sheep:

import java.util.Hashtable;    public class Sheep implements Cloneable {      Hashtable flock = new Hashtable( );        public Object clone( ) {          try {              return super.clone( );          } catch (CloneNotSupportedException e ) {               throw new Error("This should never happen!");          }      }  }

Sheep has one instance variable, a Hashtable called flock (which the sheep uses to keep track of its fellow sheep). Our class implements the Cloneable interface, indicating that it is okay to copy Sheep, and it has overridden the clone() method to make it public. Our clone() simply returns the object created by the superclass's clone() method a copy of our Sheep. Unfortunately, the compiler is not smart enough to figure out that the object we're cloning will never throw the CloneNotSupportedException, so we have to guard against it anyway. Our sheep is now cloneable. We can make copies like so:

Sheep one = new Sheep( );  Sheep anotherOne = (Sheep)one.clone( );

The cast is necessary here because the return type of clone() is Object.[1]

We now have two sheep instead of one. A properly implemented equals() method would tell us that the sheep are equivalent, but == tells us that they aren't the same; that is, they are two distinct objects. Java has made a shallow copy of our Sheep. What's so shallow about it? Java has simply copied the values of our variables. That means that the flock instance variable in each of our Sheep still holds the same information that is, both sheep have a reference to the same Hashtable. The situation looks like that shown in Figure 7-1.

Figure 7-1. Shallow copy of an object

figs/lj2.0701.gif

This may or may not be what you intended. If we instead want our Sheep to have separate copies of all its variables (or something in between), we can take control ourselves. In the following example, DeepSheep, we implement a "deep" copy, duplicating our own flock variable:

public class DeepSheep implements Cloneable {      Hashtable flock = new Hashtable( );        public Object clone( ) {          try {              DeepSheep copy = (DeepSheep)super.clone( );              copy.flock = (Hashtable)flock.clone( );              return copy;          } catch (CloneNotSupportedException e ) {               throw new Error("This should never happen!");          }      }  }

Our clone() method now clones the Hashtable as well. Now, when a DeepSheep is cloned, the situation looks more like that shown in Figure 7-2.

Figure 7-2. Deep copy of an object

figs/lj2.0702.gif

Each DeepSheep now has its own hashtable. You can see now why objects are not cloneable by default. It would make no sense to assume that all objects can be sensibly duplicated with a shallow copy. Likewise, it makes no sense to assume that a deep copy is necessary, or even correct. In this case, we probably don't need a deep copy; the flock contains the same members no matter which sheep you're looking at, so there's no need to copy the Hashtable. But the decision depends on the object itself and its requirements.

The last method of Object we need to discuss is getClass() . This method returns a reference to the Class object that produced the Object instance. We'll talk about it next.

7.2 The Class Class

A good measure of the complexity of an object-oriented language is the degree of abstraction of its class structures. We know that every object in Java is an instance of a class, but what exactly is a class? In languages like C++, objects are formulated by and instantiated from classes, but classes are really just artifacts of the compiler. Thus, in those languages you see classes mentioned only in source code, not at runtime. By comparison, classes in Smalltalk are real, runtime entities in the language that are themselves described by "metaclasses" and "metaclass classes." Java strikes a happy medium between these two languages with what is effectively a two-tiered system that uses Class objects.

Classes in Java source code are represented at runtime by instances of the java.lang.Class class. There's a Class object for every class you use; this Class object is responsible for producing instances of its class. But you don't have to worry about that unless you are interested in loading new kinds of classes dynamically at runtime. The Class object is also the basis for "reflecting" on a class to find its methods and other properties, allowing you to find out about an object's structure at runtime. We'll discuss reflection in the next section.

We get the Class associated with a particular object with the getClass() method:

String myString = "Foo!"  Class c = myString.getClass( );

We can also get the Class reference for a particular class statically, using the .class notation:

Class c = String.class;

The .class reference looks like a static field that exists in every class. However, it is really resolved by the compiler.

One thing we can do with the Class object is ask for the name of the object's class:

String s = "Boofa!";   Class myclass= s.getClass( );   System.out.println( myclass.getName( ) );   // "java.lang.String"

Another thing that we can do with a Class is to ask it to produce a new instance of its type of object. Continuing with the previous example:

try {       String s2 = (String)strClass.newInstance( );   }   catch ( InstantiationException e ) { ... }   catch ( IllegalAccessException e ) { ... }

newInstance() has a return type of Object, so we have to cast it to a reference of the appropriate type. (newInstance() has to be able to return any kind of constructed object.) A couple of exceptions can be thrown here. An InstantiationException indicates we're trying to instantiate an abstract class or an interface. IllegalAccessException is a more general exception that indicates we can't access a constructor for the object. Note that newInstance() can create only an instance of a class that has an accessible default constructor. It doesn't allow us to pass any arguments to a constructor. (But see the later section Section 7.3.4, where we'll learn how to do just that.)

All this becomes more meaningful when we add the capability to look up a class by name. forName() is a static method of Class that returns a Class object given its name as a String:

try {       Class sneakersClass = Class.forName("Sneakers");   }    catch ( ClassNotFoundException e ) { ... }

A ClassNotFoundException is thrown if the class can't be located.

Combining these tools, we have the power to load new kinds of classes dynamically. When combined with the power of interfaces, we can use new data types by name in our applications:

interface Typewriter {       void typeLine( String s );       ...   }      class Printer implements Typewriter {       ...   }      class MyApplication {       ...       String outputDeviceName = "Printer";          try {           Class newClass = Class.forName( outputDeviceName );           Typewriter device = (Typewriter)newClass.newInstance( );           ...           device.typeLine("Hello...");       }       catch ( Exception e ) { ... } }

Here we have an application loading a class implementation (Printer, which implements the Typewriter interface) knowing only its name. Imagine the name was entered by the user or looked up from a configuration file. This kind of class loading is the basis for many kinds of configurable systems in Java.

7.3 Reflection

In this section, we'll take a look at the Java Reflection API, supported by the classes in the java.lang.reflect package. As its name suggests, reflection is the ability for a class or object to examine itself. Reflection lets Java code look at an object (more precisely, the class of the object) and determine its structure. Within the limits imposed by the security manager, you can find out what constructors, methods, and fields a class has, as well as their attributes. You can even change the value of fields, dynamically invoke methods, and construct new objects, much as if Java had primitive pointers to variables and methods. And you can do all this on objects that your code has never even seen before.

We don't have room here to cover the Reflection API fully. As you might expect, the reflect package is complex and rich in details. But reflection has been designed so that you can do a lot with relatively little effort; 20% of the effort gives you 80% of the fun.

The Reflection API is used by JavaBeans to determine the capabilities of objects at runtime. It's also used at a lower level by object serialization to tear apart and build objects for transport over streams or into persistent storage. Obviously, the power to pick apart objects and see their internals must be zealously guarded by the security manager. The general rule is that your code is not allowed to do anything with the Reflection API that it couldn't do with static (ordinary, compiled) Java code. In short, reflection is a powerful tool, but it isn't an automatic loophole. By default, an object can't use it to work with fields or methods that it wouldn't normally be able to access (for example, another object's private fields), although those privileges can be granted, as we'll discuss later.

The three primary features of a class are its fields (variables), methods, and constructors. For purposes of describing or accessing an object, these three features are represented by separate classes in the Reflection API: java.lang.reflect.Field, java.lang.reflect.Method, and java.lang.reflect.Constructor. We can look up these members of a class using the Class object.

The Class class provides two pairs of methods for getting at each type of feature. One pair allows access to a class's public features (including those inherited from its superclasses) while the other pair allows access to any public or nonpublic item declared directly within the class (but not features that are inherited), subject to security considerations. Some examples:

  • getFields() returns an array of Field objects representing all a class's public variables, including those it inherits.

  • getDeclaredFields() returns an array representing all the variables declared in the class, regardless of their access modifiers (not including variables the security manager won't let you see), but not including inherited variables.

  • For constructors, the distinction between "all constructors" and "declared constructors" is not meaningful (classes do not inherit constructors), so getConstructors() and getDeclaredConstructors() differ only in that the former returns public constructors, while the latter returns all the class's constructors.

Each pair of methods includes a method for listing all the items at once (for example, getFields()) and a method for looking up a particular item by name and for methods and constructors by signature (for example, getField(), which takes the field name as an argument).

The following listing shows the methods in the Class class:

Field [] getFields();

Get all public variables, including inherited ones.

Field getField(String name);

Get the specified public variable, which may be inherited.

Field [] getDeclaredFields();

Get all public and nonpublic variables declared in this class (not including those inherited from superclasses).

Field getDeclaredField(String name);

Get the specified variable, public or nonpublic, declared in this class (inherited variables not considered).

Method [] getMethods();

Get all public methods, including inherited ones.

Method getMethod(String name, Class [] argumentTypes);

Get the specified public method whose arguments match the types listed in argumentTypes. The method may be inherited.

Method [] getDeclaredMethods();

Get all public and nonpublic methods declared in this class (not including those inherited from superclasses).

Method getDeclaredMethod(String name, Class [] argumentTypes);

Get the specified method, public or nonpublic, whose arguments match the types listed in argumentTypes, and which is declared in this class (inherited methods not considered).

Constructor [] getConstructors();

Get all public constructors of this class.

Constructor getConstructor(Class [] argumentTypes);

Get the specified public constructor of this class whose arguments match the types listed in argumentTypes.

Constructor [] getDeclaredConstructors();

Get all public and nonpublic constructors of this class.

Constructor getDeclaredConstructor(Class [] argumentTypes);

Get the specified constructor, public or nonpublic, whose arguments match the types listed in argumentTypes.

As a quick example, we'll show how easy it is to list all the public methods of the java.util.Calendar class:

Method [] methods = Calendar.class.getMethods( );  for (int i=0; i < methods.length; i++)      System.out.println( methods[i] );

Here we have used the .class notation to get a reference to the Class of Calendar. Remember the discussion of the Class class; the reflection methods don't belong to a particular instance of Calendar itself; they belong to the java.lang.Class object that describes the Calendar class. If we wanted to start from an instance of Calendar (or, say, an unknown object), we could have used the getClass() method of the object instead:

Method [] methods = myUnknownObject.getClass(  ).getMethods( );

7.3.1 Security

Access to the Reflection API is governed by a security manager. A fully trusted application has access to all the previously discussed functionality; it can gain access to members of classes at the level of restriction normally granted code within its scope. It is, however, possible to grant special access to code so that it can use the Reflection API to gain access to private and protected members of other classes in a way that the Java language ordinarily disallows.

The Field, Method, and Constructor classes all extend from a base class called AccessibleObject. The AccessibleObject class has one important method called setAccessible(), which allows you to deactivate normal security when accessing that particular class member. That may sound too easy. It is indeed simple, but whether that method allows you to disable security or not is a function of the Java security manager and security policy. You can do this in a normal Java application running without any security policy.

7.3.2 Accessing Fields

The class java.lang.reflect.Field represents static variables and instance variables. Field has a full set of overloaded accessor methods for all the base types (for example, getInt() and setInt(), getBoolean() and setBoolean()) and get() and set() methods for accessing members that are object references. Let's consider this class:

class BankAccount {      public int balance;  }

With the Reflection API, we can read and modify the value of the public integer field balance:

BankAccount myBankAccount = ...;  ...  try {      Field balanceField = BankAccount.class.getField("balance");      // read it      int mybalance = balanceField.getInt( myBankAccount );         // change it     balanceField.setInt( myBankAccount, 42 ); } catch ( NoSuchFieldException e ) {       ... // there is no "balance" field in this class } catch ( IllegalAccessException e2) {      ... // we don't have permission to access the field }

In this example, we are assuming that we already know the structure of a BankAccount object. But in general we could gather that information from the object itself.

All the data access methods of Field take a reference to the particular object instance that we want to access. In the code shown earlier, the getField() method returns a Field object that represents the balance of the BankAccount class; this object doesn't refer to any specific BankAccount. Therefore, to read or modify any specific BankAccount, we call getInt() and setInt() with a reference to myBankAccount, which is the particular account we want to work with. An exception occurs if we try to access a field that doesn't exist, or if we don't have the proper permission to read or write to the field. If we make balance a private field, we can still look up the Field object that describes it, but we won't be able to read or write its value.

Therefore, we aren't doing anything that we couldn't have done with static code at compile time; as long as balance is a public member of a class that we can access, we can write code to read and modify its value. What's important is that we're accessing balance at runtime, and we could just as easily use this technique to examine the balance field in a class that was dynamically loaded.

7.3.3 Accessing Methods

The class java.lang.reflect.Method represents a static or instance method. Subject to the normal security rules, a Method object's invoke() method can be used to call the underlying object's method with specified arguments. Yes, Java does have something like a method pointer!

As an example, we'll write a Java application called Invoke that takes as command-line arguments the name of a Java class and the name of a method to invoke. For simplicity, we'll assume that the method is static and takes no arguments:

//file: Invoke.java import java.lang.reflect.*;     class Invoke {    public static void main( String [] args ) {      try {        Class c = Class.forName( args[0] );        Method m = c.getMethod( args[1], new Class [] { } );        Object ret =  m.invoke( null, null );        System.out.println(           "Invoked static method: " + args[1]           + " of class: " + args[0]           + " with no args\nResults: " + ret );     } catch ( ClassNotFoundException e ) {        // Class.forName( ) can't find the class     } catch ( NoSuchMethodException e2 ) {        // that method doesn't exist      } catch ( IllegalAccessException e3 ) {        // we don't have permission to invoke that method      } catch ( InvocationTargetException e4 ) {        // an exception occurred while invoking that method        System.out.println(           "Method threw an: " + e4.getTargetException( ) );     }    }  }

We can run invoke to fetch the value of the system clock:

% java Invoke java.lang.System currentTimeMillis Invoked static method: currentTimeMillis of class: java.lang.System with no args   Results: 861129235818

Our first task is to look up the specified Class by name. To do so, we call the forName() method with the name of the desired class (the first command-line argument). We then ask for the specified method by its name. getMethod() has two arguments: the first is the method name (the second command-line argument), and the second is an array of Class objects that specifies the method's signature. (Remember that any method may be overloaded; you must specify the signature to make it clear which version you want.) Since our simple program calls only methods with no arguments, we create an anonymous empty array of Class objects. Had we wanted to invoke a method that takes arguments, we would have passed an array of the classes of their respective types, in the proper order. For primitive types we would have used the standard wrappers (Integer, Float, Boolean, etc.) to hold the values. The classes of primitive types in Java are represented by special static TYPE fields of their respective wrappers; for example, use Integer.TYPE for the class of an int.

Once we have the Method object, we call its invoke() method. This calls our target method and returns the result as an Object. To do anything nontrivial with this object, you have to cast it to something more specific. Presumably, since you're calling the method, you know what kind of object to expect. If the returned value is a primitive type such as int or boolean, it will be wrapped in the standard wrapper class for its type. (Wrappers for primitive types are discussed in Chapter 10.) If the method returns void, invoke() returns a java.lang.Void object. This is the wrapper class that represents void return values.

The first argument to invoke() is the object on which we would like to invoke the method. If the method is static, there is no object, so we set the first argument to null. That's the case in our example. The second argument is an array of objects to be passed as arguments to the method. The types of these should match the types specified in the call to getMethod(). Because we're calling a method with no arguments, we can pass null for the second argument to invoke(). As with the return value, you must use wrapper classes for primitive argument types.

The exceptions shown in the previous code occur if we can't find or don't have permission to access the method. Additionally, an InvocationTargetException occurs if the method being invoked throws some kind of exception itself. You can find what it threw by calling the getTargetException() method of InvocationTargetException.

7.3.4 Accessing Constructors

The java.lang.reflect.Constructor class represents an object constructor that accepts arguments. You can use it, subject to the security manager of course, to create a new instance of an object. Recall that you can create instances of a class with Class.newInstance() , but you cannot specify arguments with that method. This is the solution to that problem, if you really need to do it.

Here we'll create an instance of java.util.Date,[2] passing a string argument to the constructor:

try {      Constructor c =         Date.class.getConstructor(new Class [] { String.class } );     Object o = c.newInstance( new Object [] { "Jan 1, 2000" } );      Date d = (Date)o;      System.out.println(d);  } catch ( NoSuchMethodException e ) {      // getConstructor( ) couldn't find the constructor we described  } catch ( InstantiationException e2 ) {      // the class is abstract   } catch ( IllegalAccessException e3 ) {      // we don't have permission to create an instance  } catch ( InvocationTargetException e4 ) {      // the construct threw an exception  }

The story is much the same as with a method invocation; after all, a constructor is really no more than a method with some strange properties. We look up the appropriate constructor for our Date class the one that takes a single String as its argument by passing getConstructor() an array containing the String class as its only element. (If the constructor required more arguments, we would put additional objects in the array, representing the class of each argument.) We can then invoke newInstance(), passing it a corresponding array of argument objects. Again, to pass primitive types, we would wrap them in their wrapper types first. Finally, we cast the resulting object to a Date and print it.

The exceptions from the previous example apply here, too, along with IllegalArgumentException and InstantiationException. The latter is thrown if the class is abstract and therefore can't be instantiated.

7.3.5 What About Arrays?

The Reflection API allows you to create and inspect arrays of base types using the java.lang.reflect.Array class. The process is very much the same as with the other classes, so we won't cover it in detail. The primary feature is a static method of Array called newInstance(), which creates an array, allowing you to specify a base type and length. You can also use it to construct multidimensional array instances, by specifying an array of lengths (one for each dimension). For more information, look in your favorite Java language reference.

7.3.6 Dynamic Interface Adapters

Ideally, Java reflection would allow us to do everything at runtime that we can do at compile time (without forcing us to generate and compile source into bytecode). But that is not entirely the case. Although we can dynamically load and create instances of objects at runtime using the Class.forName() method, there is no general way to create new types of objects for which no class files preexist on the fly.

In Java 1.3, the java.lang.reflect.Proxy class was added, which takes a step towards solving this problem by allowing the creation of adapter objects that implement arbitrary interfaces. The Proxy class is a factory that can generate an adapter class implementing any interface (or interfaces) you want. When methods are invoked on the adapter class, they are delegated to a designated InvocationHandler object. You can use this to create implementations of any kind of interface at runtime and handle the method calls anywhere you want. This is particularly important for tools that work with JavaBeans, which must dynamically register event listeners. (We'll mention this again in Chapter 21.)

In the following snippet, we take an interface name and construct a proxy implementing the interface. It outputs a message whenever any of the interface's methods is invoked:

import java.lang.reflect.*;    InvocationHandler handler =   new InvocationHandler( ) {     invoke( Object proxy, Method method, Object[] args ) {         System.out.println( "Method: "+ method.getName(  ) +"( )"                             +" of interface: "+ interfaceName                             + " invoked on proxy." );         return null;     }   };    Class clas = Class.forName( MyInterface );    MyInterface interfaceProxy =     (MyInterface)Proxy.newProxyInstance(          clas.getClassLoader( ), new Class[] { class }, handler );

The resulting object, interfaceProxy, is cast to the type of the interface we want. It will call our handler whenever any of its methods are invoked.

First we make an implementation of InvocationHandler. This is an object with an invoke() method that takes as its argument the Method being called and an array of objects representing the arguments to the method call. Then we fetch the class of the interface that we're going to implement using Class.forName(). Finally we ask the proxy to create an adapter for us, specifying the types of interfaces (you can specify more than one) that we want implemented and the handler to use. invoke() is expected to return an object of the correct type for the method call. If it returns the wrong type, a special runtime exception is thrown. Any primitive types in the arguments or in the return value should be wrapped in the appropriate wrapper class. (The runtime system unwraps the return value, if necessary.)

7.3.7 What Is Reflection Good for?

In Chapter 21 we'll learn how reflection is used to dynamically discover capabilities and features of JavaBean objects. But these are somewhat behind-the-scenes applications. What can reflection do for us in everyday situations?

Well, we could use reflection to go about acting as if Java had dynamic method invocation and other useful capabilities; in Chapter 21, we'll also develop a dynamic adapter class using reflection. But as a general coding practice, dynamic method invocation is a bad idea. One of the primary features of Java is its strong typing and safety. You abandon much of that when you take a dip in the reflecting pool. And although the performance of the Reflection API is very good, it is not as fast as compiled method invocations in general.

More appropriately, you can use reflection in situations where you need to work with objects that you can't know about in advance. Reflection puts Java on a higher plane of programming languages, opening up possibilities for new kinds of applications. As we hinted earlier, one of the most important uses for reflection is in integrating Java with scripting languages. With reflection, you can write a source code interpreter in Java that can access the full Java APIs, create objects, invoke methods, modify variables and do all the other things a Java program can do at compile time, while it is running. In fact someone has done this one of the authors of this book!

7.3.7.1 The BeanShell scripting language

I (Pat) can't resist inserting a plug here for BeanShell my free, open source, lightweight Java scripting language. BeanShell is just what I alluded to in the previous section a Java application that uses the Reflection API to execute Java statements and expressions dynamically. You can use BeanShell interactively to quickly try out some of the examples in this book (although you can't create classes per se). BeanShell exercises the Java Reflection API to its fullest and serves as a demonstration of how dynamic the Java runtime environment really is.

You can find a copy of BeanShell on the CD-ROM that accompanies this book (view CD content online at http://examples.oreilly.com/learnjava2/CD-ROM/) and the latest release and documentation at its web site, http://www.beanshell.org. In recent years BeanShell has become quite popular. It is now distributed with Emacs as part of the JDE and bundled with popular application environments including BEA's WebLogic server, NetBeans, and Sun's Forte for Java IDE. See Appendix B for more information on getting started. I hope you find it both interesting and useful!

[1]  You might think that we could override the clone() method in our objects to refine the return type of the clone() method. However this is currently not possible in Java. You can't override methods and change their return types. Technically this would be called covariant return typing. It's something that may find its way into the language eventually.

[2]  This Date constructor is deprecated but will serve us for this example.

CONTENTS


Learning Java
Learning Java, Second Edition
ISBN: 0596002858
EAN: 2147483647
Year: 2002
Pages: 30

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