ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button O'Reilly Book Excerpts: Java Generics and Collections

Java Generics and Collections: Evolution, Not Revolution, Part 2

by Philip Wadler, Maurice Naftalin

Editor's Note: In last week's excerpt from Chapter 5 of Java Generics and Collections, authors Maurice Naftalin and Philip Wadler considered the situation of having a code-base that doesn't use generics, and how you can migrate to generics without having to cut completely over in one release. They portray the situation with simple examples of "library" and "client" code, and then consider the migration of the library to generics while leaving the client non-genericized. In this second part of the excerpt, they move on to the trickier case: genericizing the client, while leaving the library alone.

Legacy Library with Generic Client

It usually makes sense to update the library before the client, but there may be cases when you wish to do it the other way around. For instance, you may be responsible for maintaining the client but not the library; or the library may be large, so you may want to update it gradually rather than all at once; or you may have class files for the library, but no source.

In such cases, it makes sense to update the library to use parameterized types in its method signatures, but not to change the method bodies. There are three ways to do this: by making minimal changes to the source, by creating stub files, or by use of wrappers. We recommend use of minimal changes when you have access to source and use of stubs when you have access only to class files, and we recommend against use of wrappers.

Evolving a Library using Minimal Changes

The minimal changes technique is shown in Example 5.3. Here the source of the library has been edited, but only to change method signatures, not method bodies. The exact changes required are highlighed in boldface. This is the recommended technique for evolving a library to be generic when you have access to the source.

Example 5-3. Evolving a library using minimal changes

m/Stack.java:
  interface 
 
 Stack<E> {
    public boolean empty();
    public void push(E elt);
    public E pop();
  }

m/ArrayStack.java:
  @SuppressWarnings("unchecked")
  class ArrayStack<E> implements Stack<E> {
    private List list;
    public ArrayStack() { list = new ArrayList(); }
    public boolean empty() { return list.size() == 0; }
    public void push(E elt) { list.add(elt); }  // unchecked call
    public E pop() {
      Object elt = list.remove(list.size()-1);
      return (E)elt;  // unchecked cast
    }
    public String toString() { return "stack"+list.toString(); }
  }

m/Stacks.java:
  @SuppressWarnings("unchecked")
  class Stacks {
    public static <T> Stack<T> reverse(Stack<T> in) {
      Stack out = new ArrayStack();
      while (!in.empty()) {
        Object elt = in.pop();
        out.push(elt);  // unchecked call
      }
      return out;  // unchecked conversion
    }
  }

To be precise, the changes required are:

  • Adding type parameters to interface or class declarations as appropriate (for interface Stack<E> and class ArrayStack<E>)

  • Adding type parameters to any newly parameterized interface or class in an extends or implements clause (for Stack<E> in the implements clause of ArrayStack<E>),

  • Adding type parameters to each method signature as appropriate (for push and pop in Stack<E> and ArrayStack<E>, and for reverse in Stacks)

  • Adding an unchecked cast to each return where the return type contains a type parameter (for pop in ArrayStack<E>, where the return type is E)—without this cast, you will get an error rather than an unchecked warning

  • Optionally adding annotations to suppress unchecked warnings (for ArrayStack<E> and Stacks)

It is worth noting a few changes that we do not need to make. In method bodies, we can leave occurrences of Object as they stand (see the first line of pop in ArrayStack), and we do not need to add type parameters to any occurrences of raw types (see the first line of reverse in Stacks). Also, we need to add a cast to a return clause only when the return type is a type parameter (as in pop) but not when the return type is a parameterized type (as in reverse).

With these changes, the library will compile successfully, although it will issue a number of unchecked warnings. Following best practice, we have commented the code to indicate which lines trigger such warnings:

% javac -Xlint:unchecked m/Stack.java m/ArrayStack.java m/Stacks.java
m/ArrayStack.java:7: warning: [unchecked] unchecked call to add(E)
as a member of the raw type java.util.List
    public void push(E elt)  list.add(elt);   // unchecked call
                                      ^
m/ArrayStack.java:10: warning: [unchecked] unchecked cast
found   : java.lang.Object
required: E
      return (E)elt;  // unchecked cast
                ^
m/Stacks.java:7: warning: [unchecked] unchecked call to push(T)
as a member of the raw type Stack
        out.push(elt);  // unchecked call
                ^
m/Stacks.java:9: warning: [unchecked] unchecked conversion
found   : Stack
required: Stack<T>
      return out;  // unchecked conversion
             ^
4 warnings

To indicate that we expect unchecked warnings when compiling the library classes, the source has been annotated to suppress such warnings.

@SuppressWarnings("unchecked");

(The suppress warnings annotation does not work in early versions of Sun's compiler for Java 5.) This prevents the compiler from crying wolf—we've told it not to issue unchecked warnings that we expect, so it will be easy to spot any that we don't expect. In particular, once we've updated the library, we should not see any unchecked warnings from the client. Note as well that we've suppressed warnings on the library classes, but not on the client.

The only way to eliminate (as opposed to suppress) the unchecked warnings generated by compiling the library is to update the entire library source to use generics. This is entirely reasonable, as unless the entire source is updated there is no way the compiler can check that the declared generic types are correct. Indeed, unchecked warnings are warnings—rather than errors—largely because they support the use of this technique. Use this technique only if you are sure that the generic signatures are in fact correct. The best practice is to use this technique only as an intermediate step in evolving code to use generics throughout.

Pages: 1, 2

Next Pagearrow