ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


Generics and Method Objects

by William Grosso, author of Java RMI
11/14/2001

Abstract

In the first article in this series, I introduced a distributed translation service and showed how using a simple command object framework to encapsulate retry logic simultaneously simplified the client application and made it more robust. The second article in this series built on this framework to implement a caching system for stubs to remote servers.

In this article, I'll introduce you to the new Generics Specification (which came out of the Java Community Process) and then rebuild the command object framework using it. This won't actually change the performance of the application or add any new functionality. It will, however, add a substantial amount of compile-time type checking to the framework, thereby making the code easier to maintain and extend.

Most of this article is concerned with generic classes and generic methods (hereafter simply generics). As with the other articles in this series, this article requires a fair amount of RMI knowledge and experience; if you're not familiar with RMI, my book Java RMI is a great place to start. In addition, this article also requires a little bit of patience -- generics aren't particularly difficult, but fully understanding them does take time.

The source code for this article is available as a zip file.

Frameworks, Libraries, and Type Safety

By any count, the number of libraries available for the Java programmer is enormous. The last time I checked the JDK, it clocked in at well over 4,000 classes. The various Apache projects have at least that many classes, as well. And then there're the thousands of projects at SourceForge, and the whole Giant Java Tree and the Hypersonic database and the JBoss application server and... you get the point. There's a lot of code out there, and a lot of it is intended to be reused across multiple programs. This is a wonderful state of affairs. Having vast repositories of publicly available code -- which has been tested and retested and used and reused -- makes life easier.

But because they're intended for use across multiple projects, the frameworks and libraries out there have a significant design flaw: they're not very type safe. In fact, method signatures in most libraries are deliberately written to use what I call the weakest plausible type.

The weakest plausible type is simply the common superclass of all the plausible argument types. That is, it's the common superclass of all the classes that can be reasonably be used as arguments to a method. Consider, for example, the collection classes defined in the java.util package. A collection is a container; it holds objects. Since you can imagine storing just about any type of object in a container, Object is the weakest plausible type for most of the method signatures. And, hence, the signatures for Collection were defined using Object, as in the following two methods from the Collection interface:

    boolean add(Object o) 
    boolean contains(Object o)

Having signatures like these makes the Collections library reusable across projects. It doesn't matter than I'm writing an accounting package and you're working on a module for a distributed gaming system -- we can both store our objects in instances of Vector. But this flexibility makes the Collections library more dangerous to use in any given project. When an instance is retrieved from a collection, whether directly or via an Iterator, it has a declared type of Object. Since Object is rarely sufficient, every single programmer who uses the Collections classes winds up writing code like the following:

    // _contractors is a vector of instances of Person
    Iterator i = _contractors.iterator();
    while (i.hasNext()) {
        Person nextContractor = (Person) i.next();
        payContractor(nextContractor);
    }

This is a significant problem in Java. To the extent that a programmer is forced to use casts on return values from methods, there is the possibility of the Java runtime throwing unexpected instances of ClassCastException. And, since ClassCastException is an unchecked exception, it's very easy for an error in casting an object to cause a catastrophic error in your code.

Also in this series:

Learning Command Objects and RMI -- O'Reilly's Java RMI author William Grosso introduces you to the basic ideas behind command objects by providing a translation service from a remote server and using command objects to structure the RMI made from a client program.

Seamlessly Caching Stubs for Improved Performance -- In Part 2 of this RMI series, William Grosso addresses a common problem with RMI apps -- too many remote method calls to a naming service. In this article he extends the framework introduced in Part 1 to provide seamless caching of stubs.

How do you deal with this? Up until now, programmers only had three options:

In practice, almost everyone chooses the third option (relying on QA to catch all the mistakes before the application ships). In fact, even in the code for previous articles in this series, we've seen code that blithely casts return values to the "correct" type. For example, the signature of AbstractRemoteMethodCall's makeCall is the following:

    public Object makeCall() throws ServerUnavailable, Exception

Classes that call this method have to cast the return value correctly, as in the following lines from TranslatorPanel:

        GetRegistryContents getRegistryContents = new GetRegistryContents (_registryServerName, _registryPort);
        try {
            Vector serverNames = (Vector) getRegistryContents.makeCall();
            model.setContents(serverNames);
        }

This example is especially egregious because a few lines later, inside the setContents method of model, the elements of the Vector are cast as instances of String. That is, the code that called getRegistryContents simply knew that the return value -- even though it was declared to be an instance of Object -- was an instance of Vector. And, furthermore, it knew that the Vector only contained instances of String, even though Vector is declared to contain instances of Object. It turns out that the code for this article is correct, but wouldn't it be nice if the compiler was able to verify it?

Related Reading

Java RMIJava RMI
By William Grosso
Table of Contents
Index
Sample Chapter
Full Description

And that's the goal of the generics specification. By using generics, a programmer can convert instances of ClassCastException thrown at run-time errors into compile-time errors. This results in fewer programming errors and better code.

Using generics also saves time. If types are explicitly declared, then it's easy for programmers to figure out the return type of a method, or to determine the types of objects stored in a Vector. If generics aren't used, the alternative is a frustrating rummage through the codebase, looking for a helpful comment or two (something like //This vector can only contain instances of Contractor).


A Brief Introduction to Generic Classes

The problem with ClassCastException is nothing new, of course. Programmers have been running into this sort of problem, and generally hoping that their casts are correct, ever since the first JDK shipped. What is new is that there's a solution on the horizon: the Java Community Process, through JSR-104, has produced a specification for adding generics to the Java language.

How to Obtain the Generics Package

For most people, reading a description is a poor substitute for actually making mistakes. Even though I'm going to tell you how to use generics in some detail, it makes sense for you to download and install the generics package first. That way, as I'm explaining things, you can run tests and try things out.

The generics package actually consists of two separate downloads: a specification and a early-access implementation of the specification. But, before I tell you how to download either of these things, I want to make a few things clear:

Put another way, using this stuff is going to force you to migrate to a new, still-in-beta, version of the JDK. And even if you manage to get your friends to migrate to JDK 1.4, they still aren't going to be able to compile your code (they will be able to use your class files; the output from the generics compiler is 100% compatible with the JDK1.4 JVM).

The generics specification is available from the JSR-014 web page (you'll have to accept a click-through license). To get the early-access implementation of the generics specification, you'll have to go to Javasoft's early access site.

The implementation that you can download from the early access site consists of two major components: a new compiler (that understands the generics syntax) and a genericized implementation of the Collections library.

The Idea Behind Generics

The idea behind generics is simple, and will look familiar to programmers who have used C++ templates. As part of class, interface, and method definitions, you can specify parametrized types by using a comma-separated list of type variables. The type variables stand in for classes that will be specified later, either as part of a subclass definition or during a call to a constructor.

The code that creates instances of a generic class is responsible for specifying all the parametrized types; the compiler is responsible for tracking the types and making sure that all the type declarations match throughout the code (you don't even have to include the casts in your code; the compiler will handle everything for you).

Here, for example, is the generic definition of the List class and the add method (the generic version of List is part of the early-access implementation).

    public interface List<E> extends Collection<E>{
        // ..
        boolean add(E o)
        // ...

This declares that List is an interface with a single parametrized type, denoted by the type variable E. Arguments to the add method have to have the same type as E. In order for an instance of List to be created, E must be specified. And, since creation is done by invoking a constructor, the compiler can actually check that the arguments to add are actually of the correct type and warn the programmer (thus converting run-time instances of ClassCastException into compile-time warnings and errors).

List is a simple example, but it illustrates the basic idea: as part of the declaration, programmers include a set of type variables, using the <> syntax. Once the type variables have been declared, they can be used to restrict the arguments, return values, or exceptions in method declarations.

Here's some more class declarations, to give you the general idea:

    public class SimpleClass<T> { ... }
    public class MoreComplicatedClass<W, T> { ... }

The first of these defines a class named SimpleClass that has a single type variable (T). The second defines a class, MoreComplicatedClass, with two type variables (W and T). In the case of SimpleClass , methods can only use one type variable; in the case of MoreComplicatedClass, there are two type variables involved.

Note: I'm deliberately not giving the full grammar for generics or covering all the possibilities. This article only covers the basics of generics. For the full scoop, see JSR-014.

One unexpected feature is that any type variable can be constrained by using the extends keyword. For example, the following declaration says that the class Athlete is a parametrized class with two type variables, T and W, and that T is restricted to subclasses of Sport.

    public class Athlete<T extends Sport, W> extends Person

Instantiating and Using a Generic Class

Once you've defined a generic class (or found one to use), the next thing you need to do is use it in your code. The best way to explain this is to start with an example. Let's look at the declaration of the generic versions of the List interface, the AbstractList class, and the Vector class. Here's the relevant code:

    public interface List<E> extends Collection<E>{
        boolean containsAll(java.util.Collection<T> c)
        boolean removeAll(java.util.Collection<T> c)
        boolean retainAll(java.util.Collection<T> c)
        T[] toArray(T[] a)
        boolean add(E o)
        //    ...
    }

    public class AbstractList<E> implements List<E>{
        //    ...
    }

    public class Vector<E> extends AbstractList<E>{
        //    ...
    }

The only thing necessary in order to use Vector<E> is to make sure that the type variable is passed in to the constructor. After that, you can use the generic class just like a normal class. Here's an example program that creates two instances of Vector that contain strongly typed entries.

    import java.util.*;
    public class SimpleVectorProgram {
        public static void main(String[] args) {
            playWithIntegerVector();
            playWithStringVector();
        }

        private static void playWithIntegerVector() {
            Vector<Integer> integerVector = new Vector<Integer>();
            integerVector.add(new Integer(13));
            examine(integerVector);
        // no cast necessary in next line
            Integer ourInteger = integerVector.get(0);
        }

        private static void playWithStringVector() {
            Vector<String> stringVector = new Vector<String>();
            stringVector.add("thirteen");
            examine(stringVector);
        // no cast necessary in next line
            String ourString = stringVector.get(0);
        }

        private static void examine(Object o) {
            System.out.println("It's an instance of " + o.getClass().getName());
        }
    }

The two different playWith... methods create two different instances of Vector -- one that can only contain instances of Integer and one that can only contain instances of String. The important point to notice is that once the vectors are created, objects are inserted and removed just as they were in JDK 1.3 or 1.2. The only difference is that the values don't need to be cast to the correct type because the compiler is performing all the necessary type checks.

The casting is still happening, of course. It's just that the casts are being inserted by the compiler, after the compiler validates that the cast will succeed. Because the compiler is checking types more thoroughly, it can reject incorrect code. For example, the following version of playwithIntegerVector won't compile:

        private static void playWithIntegerVector() {
            Vector<Integer> integerVector = new Vector<Integer>();
        // The next line causes a compiler error; it's adding an instance of
        // the wrong type. It wouldn't fail if the declaration was
        // Vector integerVector = new Vector();
            integerVector.add("thirteen");
            examine(integerVector);
            Integer ourInteger = integerVector.get(0);
        }

Generics in Gory Detail

In this section, we're going to dive into the details of the implementation of generics. This is a good time to pause and take a breather (or even to simply skip ahead; on a first reading, you might simply want to skip ahead to the the section entitled Adding Generic Arguments and ReturnValues to the Command Object Framework).

One thing that's a little surprising is the output of SimpleVectorProgram above. It prints out the following:

    It's an instance of java.util.Vector
    It's an instance of java.util.Vector

In particular, the run-time types of the vectors are Vector, and not Vector<Integer> or Vector<String>. You might suspect that this is just the result of of a badly written toString method in the Vector class, but it's not. The types Vector<String> and Vector<Integer> simply do not exist at run-time. In fact, they don't really even exist during compile-time; the compiler rewrites the source code to eliminate the type variables (the JSR-014 specification calls this rewriting process erasure).

In fact, if you decompile the byte code file for SimpleVectorProgram, you'll find the following:

    public class SimpleVectorProgram {
        public static void main(String args[]) {
            playWithIntegerVector();
            playWithStringVector();
        }

        private static void playWithIntegerVector() {
            Vector vector = new Vector();
            vector.add(new Integer(13));
            examine(vector);
            Integer integer = (Integer)vector.get(0);
        }

        private static void playWithStringVector() {
            Vector vector = new Vector();
            vector.add("thirteen");
            examine(vector);
            String s = (String)vector.get(0);
        }

        private static void examine(Object obj) {
            System.out.println("It's an instance of " + obj.getClass().getName());
        }
    }

This example shows that the generics compiler doesn't leave any traces of the extended type information (the values of the type variables) in the class files. Instead, it does two things:

This way of doing things has three main advantages:

On the other hand, this approach can also cause some headaches. The most annoying "gotcha" in the generics specification is this: you can't use parametrized types effectively in method dispatch -- they simply don't work the way ordinary types do. For example, the following class won't compile:

    public class MoreComplexVectorProgram<W,T> {
        private void examine(Vector<T>vector) {
        }
        private void examine(Vector<W> vector) {
        }
    }

This is a perfectly natural thing to want to do. Mentally, Vector<T> and Vector<W> are distinct types and so overloading the examine method is intuitively correct (if somewhat strange). But look at the decompiled code again. Once the generics compiler is finished erasing the type variable information, both examine methods will have the same signature (they both take a single argument of type Vector). Since you can't have two methods in the same class with identical signatures, this code won't compile.

Another variation of this problem arises when you try to write an equals method. Object defines equals with the following signature:

        public boolean equals(Object object)

Rather than implementing a strongly-typed version of this, it's often good practice to correctly implement this signature, because you cannot guarantee that the caller knows the type of the object it is passing to equals. If the caller doesn't know the type of the object it is passing in, then it might call the equals method with the signature equals(Object object), and you might wind up with a incorrect negative return value. So most programmers end up writing something like the following:

        public boolean equals(Object object) {
            // if the other instance doesn't have the same class,
            // they're not equal            
            if (object.getClass() != this.getClass()) {
                return false;
            }
            // okay. it's the same class. let's cast it and
            // *really* check for equality.
        }
    }

But this way of thinking can cause problems. Consider the following class:

    public class Athlete<T extends Sport> {
        private int _number;
        private String _team;

        // ....
        public boolean equals(Object object) {
            if (object.getClass() != this.getClass()) {
                return false;
            }
        // If they have the same number and are on the same team,
        // they're the same person
            Athlete otherAthlete = (Athlete) object;
            if (_number != otherAthlete.getNumber()) {
                return false
            }
            if (_team != otherAthlete.getTeam()) {
                return false
            }
            return true;
        }
    }

In this admittedly contrived example, we have the Athlete class defined as a generic class with a single type variable named T. And the equals method has a certain face validity, until you realize that getClass returns Athlete, not Athlete<Football> or Athelete<Baseball>. As it stands, this code would claim that the football player Will Allen (number 25 on the Giants) is the same as the baseball player Barry Bonds (number 25 on the Giants). Intuitively, I expect this problem to be eliminated by the first if, which checks the classes.

The moral of the story is: be careful (or be prepared to defend the claim that Will Allen is the best slugger since Babe Ruth). In this case, I'd probably define FootballPlayer and BaseballPlayer to be explicit subclasses of Athlete

Generics and Inheritance

Another interesting question revolves around how generics works with inheritance. Recall that, in part 2 of this series, we had a whole hierarchy of command objects. In particular, we had an abstract base class, AbstractRemoteMethodCall; an abstract subclass, ServerDescriptionBasedRemoteMethodCall; and a concrete subclass TranslateWord.

In general, subclasses rarely add new type variables. Type variables represent extra flexibility in how a class can be used, and abstract superclasses are "more generic" than concrete subclasses. Put more concretely, by the time we get around to writing a very specific class like TranslateWord, any flexibility as to what the argument or return value types can be ought to be gone. This is summarized in the following UML diagram:

Diagram.
The relationship between inheritance and genericity in our command object framework. Generally speaking, subclasses are more constrained (and less generic) than superclasses.

In particular, we're going to make AbstractRemoteMethodCall's makeCall method generic, so that both the return value and the exceptions thrown from makeCall can be strongly typed. Here's what the class and method declarations will look like for all three classes.

    public abstract class AbstractRemoteMethodCall<T, E extends Exception> {
        public T makeCall() throws ServerUnavailable, E {
            // ...
        }
        protected abstract T performRemoteCall(Remote remoteObject) throws RemoteException, E;
        // ...
    }

    public abstract class ServerDescriptionBasedRemoteMethodCall<T, E extends Exception > extends
        AbstractRemoteMethodCall<T, E>{
    //    ...
    //    no declaration for performRemoteCall; it's left abstract.
    }

    public class TranslateWord extends
        ServerDescriptionBasedRemoteMethodCall<Word, CouldNotTranslateException>  {
    //    ...
        protected abstract Word performRemoteCall(Remote remoteObject) throws RemoteException;
    }

Note what we did. When we defined ServerDescriptionBasedRemoteMethodCall<T, E >, we simply declared it to extend AbstractRemoteMethodCall<T, E>. This means that when the parametrized types for an instance of ServerDescription are specified, they will be used by the code for AbstractRemoteMethodCall.

There's a very important catch here -- this relies on the order of the type variables, and not their names. The following code, which switches the order of arguments and sets the superclasses' T type variable to the value of the subclass's W type variable, is perfectly valid (if somewhat confusing):

    public class Superclass<T>{
    // ...
    }
    public class Subclass<T,W> extends Superclass<W> {
    // ...
    }

The other way to deal with generic types within inheritance is to simply specify the concrete types directly, as part of the subclass declaration. We did this with TranslateWord, when we wrote extends ServerDescriptionBasedRemoteMethodCall<Word, CouldNotTranslateException>. This line of code automatically tells the compiler "Whenever someone creates an instance of TranslateWord, T is mapped to Word and E is mapped to Exception."

This is all pretty straightforward; the important point to note is that you can't deal with the problem dynamically, or at runtime. Code like the following, which attempts to set the type variable from within a constructor, just won't work:

    public class TranslateWord extends ServerDescriptionBasedRemoteMethodCall<T> {
        public TranslateWord() {
            super<Word>();

When you think about it, this is reasonable -- the whole goal of the generics specification is to enable the compiler to check types at compile-time.

Generics and Exceptions

The last remaining topic I'm going to cover in this whirlwind tour of JSR-014 is how to use generic exceptions. By now, you should have some intuition about how they're going to work: the class definition will include a parametrized type for an exception and the method definition will simply declare that it throws the type variable. In fact, we surreptitiously did exactly that in the previous section. Here's the example code:

    public abstract class AbstractRemoteMethodCall<T, E>{
        public T makeCall() throws ServerUnavailable, E {
            // ...
        }
        //...
    }

In this code snippet, AbstractRemoteMethodCall is declared to take two type variables, T and E. T is used as the return value on makeCall and E is used for exceptions. This works, and makes the code a little bit more typesafe (recall that, previously, makeCall was declared to throw Exception).

To be honest, though, I'm a little disappointed in the way exceptions are handled. The basic problem is this: while you can throw generic exceptions, you can't specify a different number of exceptions in the subclass. I'd love to have some notion of variable length generic types for exceptions so that something like the following code would mean "where the superclass claims to throw E, the subclass wants the compiler to interpret E as being one of these four types (all of which must be caught over on the client side)":

    public abstract class AbstractRemoteMethodCall<T, E>{
        public T makeCall() throws ServerUnavailable, E {
            // ...
        }
        //...
    }

    public abstract class Subclass<T>extends  
        AbstractRemoteMethodCall<T, oneof A&B&C&D> {
    
        public T makeCall() throws ServerUnavailable, E {

As matters stand, the subclass must declare that it throws some common superclass of A, B, C, and D. But that's a minor quibble. The ability to narrow exceptions in frameworks and libraries is extraordinarily useful. To see why, consider a method that, because it will be implemented by a variety of subclasses, is declared as throwing Exception. For example,

        public T performComputation(W argument) throws Exception {

The problem with this is that the code inside performComputation isn't forced to catch any exceptions. It doesn't even have to catch the exceptions that are local problems (e.g. exceptions that the superclass doesn't know how to handle or correct).

Suppose, for example, one implementation of performComputation contained some JDBC calls. Ordinarily, JDBC code has to be wrapped in a try/catch block to catch instances of SQLException. SQLException is a checked exception and the compiler will make sure that it, or a superclass, is caught somewhere in the stack frame. Which means that if performComputation wasn't declared as throwing Exception, the method wouldn't even compile until the possibility of an SQLException was explicitly addressed.

But since performComputation is declared to throw Exception, the compiler won't complain about SQLException. Instead, when an instance of SQLException is thrown, will be propagated up the stack, to eventually be caught someplace else.

On the other hand, consider the following declaration:

        public T performComputation(W argument) throws E {

In this method, any exceptions that don't extend E must still be checked locally. This means that the compiler will force you to deal with some exceptional conditions locally, which is what you probably want. In the case of JDBC calls, as long as E isn't a superclass of SQLException, the implementation of until performComputation will have to address the possibility of an SQLException.

Note:. This leads to an important design tip -- always use a separate exception hierarchy for exceptions in your program. If you throw generic exceptions, or overload ones that are defined by frameworks you happen to be using, you're working against the compiler.

Adding Generic Arguments and Return Values to the Command Object Framework

If you've read this far, and especially if you read the previous section, you're probably thinking something like "Ouch! My head feels like it's been put through a meat-grinder! What is the point of all this stuff?" Well, remember the goal of the generics specification I stated a few pages back:

By using generics, a programmer can convert casting errors from run-time exceptions to compiler errors. This results in fewer programming errors and better code.

In this section, we're going to systematically convert the command objects framework into code that uses generics. The first class we're going to convert is RemoteStubCache.

Recall that RemoteStubCache is a local cache of stubs used by an RMI client. Unless stubs are cached locally, an RMI client has to make two remote method calls for every "logical remote call": one remote call to a naming service and one remote call to the actual remote server. Converting RemoteStubCache is pretty easy: the only thing that must be changed is the internal use of Hashtable. In fact, only two lines of code changed. Here's the new version of RemoteStubCache:

    public class RemoteStubCache
    {
        // this next declaration used to be
        //    private static Hashtable _serverDescriptionsToStubs...

        private static Hashtable<ServerDescription, RemoteStub>_serverDescriptionsToStubs= new Hashtable<ServerDescription, RemoteStub>();

        public static RemoteStub getStubToRemoteObject(ServerDescription serverDescription) throws ServerUnavailable {
        // no cast necessary when pulling values from the hashtable
            RemoteStub returnValue = _serverDescriptionsToStubs.get(serverDescription);
            if (null == returnValue) {
                returnValue = serverDescription.getStub() ;
                if (null!= returnValue) {
                    _serverDescriptionsToStubs.put(serverDescription, returnValue);
                }
                else {
                    throw new ServerUnavailable();
                }
            }
            return returnValue;
        }
        public static void noLongerUsingStubToRemoteObject(ServerDescription serverDescription) {
        /*
            Needs to be implemented if we take cleaning up the cache
            seriously.
        */
        }
    
        public static void removeStubFromCache(ServerDescription serverDescription)  {
            _serverDescriptionsToStubs.remove(serverDescription);
        }
    }

AbstractRemoteMethodCall is trickier. We're going to add two type variables. The first type variable will be used for the value returned from from makeCall and the second will restrict the range of allowable exceptions. Here's the code:

    public abstract class AbstractRemoteMethodCall<T, E extends Exception> {
        public T makeCall() throws ServerUnavailable, E {
            RetryStrategy strategy = getRetryStrategy();
            while (strategy.shouldRetry()) {
                Remote remoteObject = getRemoteObject();
                if (null==remoteObject) {
                    throw new ServerUnavailable();
                }
                try {
                    return performRemoteCall(remoteObject);
                }
                catch (RemoteException remoteException) {
                    try {
                        remoteExceptionOccurred(remoteException);
                        strategy.remoteExceptionOccurred();
                    }
                    catch (RetryException retryException) {
                        handleRetryException(remoteObject);
                    }
                }
            }        
            return null;
        }
    
        protected abstract Remote getRemoteObject() throws ServerUnavailable;

        protected abstract T performRemoteCall(Remote remoteObject) throws RemoteException, E;

        protected RetryStrategy getRetryStrategy() {
            return new AdditiveWaitRetryStrategy();
        }

         protected void remoteExceptionOccurred(RemoteException remoteException) {
            /* ignored in based class. */
        }

        protected void handleRetryException(Remote remoteObject) throws ServerUnavailable {
            ExceptionLog.reportException("Repeated attempts to communicate with " + remoteObject + " failed.");
            throw new ServerUnavailable();
        }
    }

The point of this is the change in the definition of the abstract method performRemoteCall. In the previous two articles in this series, makeCall and performRemoteCall were defined as follows:

        public Object makeCall() throws ServerUnavailable, Exception {
            // Literally the exact same code...
        }
        protected abstract Object performRemoteCall(Remote remoteObject) throws RemoteException, Exception;

The new version defines them using two type variables, T and E. Concrete subclasses can stipulate the value of T and E, as in the following declaration of TranslateWord:

    public class TranslateWord extends ServerDescriptionBasedRemoteMethodCall<Word, CouldNotTranslateException>>  {
        private Word _sourceWord;
        private Language _targetLanguage;
        public TranslateWord(ServerDescription serverDescription, Word sourceWord, Language targetLanguage) {
            super(serverDescription);
            _sourceWord = sourceWord;
            _targetLanguage = targetLanguage;
        }

        // note how return type of performRemoteCall matches template variable.
        // it also matches the return type defined in the Translator interface
        protected Word performRemoteCall(Remote remoteObject) throws RemoteException, CouldNotTranslateException {
            Translator translator = (Translator) remoteObject;
            return translator.translate(_sourceWord, _targetLanguage);
        }
    }

The result of this is that code in ClientFrame that uses the TranslateWord command object to perform a remote method invocation doesn't need to cast the return value from makeCall. Here's the new code:

            TranslateWord translateMethod = new TranslateWord(translatorServerDescription, word, targetLanguage);
            try {
                // no cast in next line
                Word result = translateMethod.makeCall();

Adding Generic Exceptions to the Command Object Framework

As I mentioned above, it would be absolutely great to be able to use a type variable to indicate a variable length number of exceptions. Unfortunately, this is not possible in the current generics specification. The best we can do is to use a single type variable, which is usually E, and then use as narrow a class as possible when creating instances. Let's look once more at AbstractRemoteMethodCall

    public abstract class AbstractRemoteMethodCall<T, E extends Exception> {
        public T makeCall() throws ServerUnavailable, E {
        // ...
        }

        protected abstract T performRemoteCall(Remote remoteObject) throws RemoteException, E;
        // ...
    }

In TranslateWord, we narrow T and E as follows:

    public class TranslateWord extends ServerDescriptionBasedRemoteMethodCall<Word, CouldNotTranslateException>> {
        private Word _sourceWord;
        private Language _targetLanguage;
        public TranslateWord(ServerDescription serverDescription, Word sourceWord, Language targetLanguage) {
            super(serverDescription);
            _sourceWord = sourceWord;
            _targetLanguage = targetLanguage;
        }

        protected Word performRemoteCall(Remote remoteObject) throws RemoteException, CouldNotTranslateException {
            Translator translator = (Translator) remoteObject;
            return translator.translate(_sourceWord, _targetLanguage);
        }
    }

And then, in ClientFrame, TranslateWord is used in a single place, inside a single try/catch block:

            try {
                Word result = translateMethod.makeCall();
                resultText = result.toString();
            }
            catch (CouldNotTranslateException CNTE) {
                resultText = COULD_NOT_TRANSLATE_STRING;
            }
            catch (ServerUnavailable  SUA) {
                resultText = SERVER_UNAVAILABLE_STRING ;
            }
/*
    the old version of Translate word had to catch a generic exception here
            catch (Exception e) {
                resultText = e.toString();
            }
*/

            finally {
                _resultsPanel.setText(resultText);
            }

The point of this is that invocation to makeCall only needs to catch the exceptions that are actually thrown by makeCall. Using a type variable lets us narrow the range of thrown exceptions: the superclass' ServerUnavailable and E become E becomes CouldNotTranslateException in TranslateWord. The value of eliminating catch (Exception e) should not be underestimated.

There is one final wrinkle to consider: suppose the method on the server doesn't throw any exceptions (other than RemoteException). What should E be declared as? Well, one trick, which I'm simultaneously proud to have come up with and ashamed to use, is to bind the type variable E to RuntimeException, as in the following definition of GetRegistryContents.

    public class GetRegistryContents extends AbstractRemoteMethodCall<Collection<String>, RemoteException> {
        //...

        protected Collection<String> performRemoteCall(Remote remoteObject) throws RemoteException {
        // ...
        }

        // ...
    }

This actually works! RuntimeException is a subclass of Exception and so the compiler is happy (recall that E was defined to extend Exception). At the same time, RuntimeException explicitly does not need to be caught, and so the client code doesn't wind up having extraneous catch blocks. Indeed, here's the code from TranslatorPanel that uses GetRegistryContents:

        GetRegistryContents getRegistryContents = new GetRegistryContents (_registryServerName, _registryPort);
        try {
            Collection<String> serverNames = getRegistryContents.makeCall();
            model.setContents(serverNames);
        }
        catch (ServerUnavailable logged) {
            ExceptionLog.reportException(logged);
            showRemoteErrorMessage();
        }

Note: Another interesting facet of GetRegistryContents is that the type variable T is set to Collection<String>. Generics can be used recursively.

Summary

This article is the last one (for now) in this series. In the first article, I introduced a command object framework to encapsulate remote method calls in RMI. In the second article, I extended the command object framework to seamlessly implement a local stub cache behind the scenes. In doing so, I also removed the lookup code from various places in the client, instead putting most of it into a new abstract command object, ServerDescriptionBasedRemoteMethodCall. And at this point, the cost-benefit analysis looks like the following:

Related Reading

Java RMIJava RMI
By William Grosso
Table of Contents
Index
Sample Chapter
Full Description

Pro

Con

It's a long list of benefits versus a single complaint. And, just between us, I don't think the complaint is all that justified.

William Grosso is a coauthor of Java Enterprise Best Practices.


Also in this series:

Learning Command Objects and RMI -- O'Reilly's Java RMI author William Grosso introduces you to the basic ideas behind command objects by providing a translation service from a remote server and using command objects to structure the RMI made from a client program.

Seamlessly Caching Stubs for Improved Performance -- In Part 2 of this RMI series, William Grosso addresses a common problem with RMI apps -- too many remote method calls to a naming service. In this article he extends the framework introduced in Part 1 to provide seamless caching of stubs.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.