IBM Skip to main content
Search for:   within 
      Search help  
     IBM home  |  Products & services  |  Support & downloads   |  My account

developerWorks > Java technology
developerWorks
Diagnosing Java code: Java generics without the pain, Part 4
Discuss71 KBe-mail it!
Contents:
Mixins versus wrapping
Mixins and generic classes
Mixins and type erasure
Available constructors of the superclass
Accidental method overriding
With power comes problems
Resources
About the author
Rate this article
Related content:
Diagnosing Java code series
Catching more errors at compile time with Generic Java
Automatic Code Generation from Design Patterns
Subscriptions:
dW newsletters
dW Subscription
(CDs and downloads)
How generic types can conquer mischievous mixins

Level: Intermediate

Eric E. Allen (mailto:eallen@cs.rice.edu?cc=&subject=Java generics without the pain, Part 4)
Ph.D. candidate, Java programming languages team, Rice University
13 May 2003

Column iconJava developer and researcher Eric Allen concludes his four-part discussion of generic types in JSR-14 and Tiger by discussing the ramifications of adding support for mixins through generic types. Share your thoughts on this article with the author and other readers in the accompanying discussion forum. (You can also click Discuss at the top or bottom of the article to access the forum.)

So far in this mini-series discussing generic types in JSR-14 and Tiger, we've covered:

  • Generic types and the upcoming features designed to support them

  • The limitations on primitive types, constrained generics, and polymorphic methods

  • Several limitations imposed in these Java extensions

  • How the limitations are necessitated by the implementation strategy used by the compilers of these extended languages

  • The ramifications of adding support for new operations on "naked" type parameters in generic types

This month, we'll conclude our discussion of generic types in the Java language by covering the issues that need to be addressed before you can handle mixins -- perhaps the most powerful feature that generic types promise.

Mixins versus wrapping
Mixins are classes parameterized by their parent class. For example, consider the following generic class, which extends its own type parameter:


class Scrollable<T> extends T {...}

Don't miss the rest of this series

Part 1, Introduction to generic types and support features (February 2003)

Part 2, Extension limitations and implementation strategies (March 2003)

Part 3, Adding support for new operations (April 2003)

The intention of class Scrollable is that it embeds the functionality necessary for adding scrollability to a GUI widget. Each application of this generic class would extend a distinct parent class. For example, Scrollable<JTextPane> would be a subclass of JTextPane and Scrollable<JEditorPane> would be a subclass of JEditorPane. Contrast this way to embed functions with the current functionality in the Java Swing library in which a JComponent must be "wrapped" in a JScrollPane if we want to make it scrollable.

Not only does wrapping require adding forwarding methods to access the functionality of the wrapped class, it also prevents us from using the resulting scrollable object in contexts where an instance of the wrapped object is needed (for instance, we can't pass a JScrollPane to a method requiring an instance of JTextPane). By parameterizing Scrollable by its parent class, we are able to keep a single point of control for the functionality involved in scrolling while extending multiple superclasses. In this way, being able to use mixins gives us back some of the power of multiple inheritance but without the accompanying pathologies.

In the previous example, we could even put a constraint on a type parameter to prevent it from being used in inappropriate contexts. For instance, we might want to constrain the type parameter to be a subclass of JComponent:


class Scrollable<T extends JComponent> extends T {...}

Then only GUI components could be extended by our mixin.

Mixins and generic classes: A perfect match
Often, mixins are added to a language as an independent language feature, as is done in Jam. But it is appealing, almost seductive, to incorporate mixins as part of a generic type system. The reason: both mixins and generic classes can be thought of as functions mapping existing classes to new classes.

Generic classes can be viewed as functions mapping their arguments to new instantiations. Mixins can be viewed as functions mapping existing classes to new subclasses. By incorporating mixins using generic types, we are able to work around many of the key limitations of other formulations of mixins.

In the Jam extension to the Java language, the type of the superclass of a mixin has no name; we simply can't refer to it in the body of the mixin. This limitation snowballs to include all sorts of other problems. For example, in Jam, the programmer is not allowed to pass this as an argument to a method; there is no way to type check such calls. That limitation is crippling because many of the most common design patterns rely on being able to pass this as an argument.

Consider the visitor pattern in which a visitor class is defined with a for method for each class in a composite hierarchy. Typically the class being visited includes an accept method that takes a visitor and calls a method on that visitor, passing in this. Thus, in Jam, the visitor pattern can't be used with mixins.

With mixins formulated as generic classes, we always have a handle on the parent class, the type parameter that the class extends. For example, we can refer to the parent class of Scrollable as type T. As a result, there are no fundamental difficulties with allowing this to be passed as a type argument.

However, there are other significant difficulties with formulating mixins as generic types. Just to give you a taste of some of the difficulties that can arise, we'll discuss a few prominent ones and some potential solutions.

Mixins and type erasure
Before discussing any other problems, we should point out that, like the feature extensions of generic types discussed last month, support for mixins can't be added to the Java language using the simple type erasure strategy used by JSR-14 and Tiger.

To see why, consider what would happen when a class that extended a type parameter was erased. It would end up extending the bound of the type parameter! For example, every instantiation of class Scrollable in the previous example would end up extending class JComponent. That's clearly not what we want.

To support mixins through generic types, we need to have run-time representations of the generic type instantiations available. Fortunately, there are ways of encoding this information that are actually backward compatible with Tiger. Such a backward-compatible encoding scheme is the hallmark trait of the NextGen formulation of Generic Java (in the Resources section).

Available constructors of the superclass
An immediate and pressing problem that arises as soon as we want to allow for classes that extend a type parameter is to decide what super-constructors are we able to call? Recall that every Java class constructor must call a constructor of the superclass. Normally, the type checker ensures that these super-constructor calls will succeed by looking up the superclass and making sure that a matching super-constructor exists.

But when all we know about our superclass is that it's some instantiation of a type parameter, we have no idea what constructors will be available for a given instantiation. Also notice that the type checker can't even check that every instantiation of a mixin will result in valid super-constructor calls. The reason: a mixin's parameters may be instantiated with type parameters bound in some other context.

For example, a generic class JSplitPane<T> may create an instance of Scrollable<T>. We can't know whether the super-constructors called in Scrollable<T> are valid unless we know all the ways in which type parameter T is instantiated for JSplitPanes. But because Java coding allows for separate class compilation, we can't know all of the instantiations of JSplitPane during type-checking.

The various solutions to this problem correspond exactly to the solutions proposed for checking new expressions on type parameters we discussed in Part 3 last month, because both super-constructor calls and new expressions reference the same class constructors of a given class. Let's review those solutions:

  • Require a zeroary constructor for all type parameter instantiations.
  • Throw an exception at run time when there is no matching constructor.
  • Include additional annotations on type parameters telling us which constructors those instantiations must contain.

As in the case of new expressions, the first two solutions have serious drawbacks. Often, it just doesn't make sense to include a zeroary constructor in a class definition. Also, it's not ideal to simply throw an exception when no matching constructor exists. After all, the whole point of static type checking is to prevent exactly that sort of exception.

The third solution can be wordy, but it has many advantages. Annotate type parameters with a set of constructors that all instantiations must have. These annotations tell us exactly what constructors we can reliably call on a type parameter. Thus, when a type parameter T is used as the superclass of a generic class, the annotation on T tells us exactly what super-constructors we can call. If T doesn't include an annotation, then the type checker disallows its use as the superclass.

Accidental method overriding
One really big problem that arises with any formulation of mixins is that the method names of a particular mixin may clash with the method names of a potential instantiation of its superclass. For example, suppose that class Scrollable contained a method getSize that took no arguments and returned a Size object that encoded its horizontal and vertical dimensions. Now let's suppose that class MyTextPane (a subclass of JComponent) also included a method getSize that took no arguments but returned an int representing the screen area of the object on which it was called.

The resulting classes are shown as follows:

Listing 1. An example of an accidental method override

class Scrollable<T extends JComponent> extends T { 
  ... 
  Size getSize() {...}
}

class MyTextPane extends JComponent { 
  ... 
  int getSize() {...}
}

new Scrollable<MyTextPane>() 

Then the mixin instantiation Scrollable<MyTextPane> would contain two methods getSize with identical (empty) parameter types, but incompatible return types! Because we could not have expected this problematic override of getSize to be foreseen by either the programmer of class Scrollable or by the programmer of MyTextPane (after all, they may not even be on the same development team), we call it an accidental override.

When mixins are formulated as generic classes, the problem of accidental overrides is particularly nasty. Because a mixin's parent may be instantiated with a type parameter, there is no way for the type checker to determine all cases of accidental method overriding. What's more, throwing a run-time exception when an accidental override occurs is not acceptable because there is no way for a client programmer to predict when such an exception will be thrown. If we want to write reliable programs, we can't allow for unpredictable errors to occur at run time.

Another solution would be to simply hide one of these clashing methods and resolve all matching method calls to refer to the method not hidden. The problem with this solution is that we'd like a mixin instantiation such as Scrollable<MyTextPane> to be used in both contexts in which a Scrollable object is called for and in contexts in which a MyTextPane object is called for. Hiding either one of the getSize methods would prevent the use of Scrollable<MyTextPane>s in both of these contexts.

In the context of mixins outside of generic types, Felleisen, Flatt, and Krishnamurthi proposed a good solution to this problem at the 1998 ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages (see Resources): to resolve references to clashing methods based on the context in which the mixin instantiation is used. In this solution, a mixin is associated with a view that determines which method to call in the case of a name clash.

In the case of mixins as generic types, we can apply the same solution. We just have to devise some notion of view that works in the context of generic types and also allows for backward compatibility with the JVM. At the Rice JavaPLT labs, we've proposed one such solution in the paper "A First-Class Approach to Genericity" (see Resources).

With power comes problems
As the examples, problems, and potential solutions demonstrate, extending generic types in Java programming to include support for mixins results in a powerful language, but it also introduces problems to overcome. This is typical of programming-language design: a desirable feature can be added only by complicating many existing features. In the world of programming languages, there's no such thing as a free lunch.

Resources

  • Participate in the discussion forum on this article. (You can also click Discuss at the top or bottom of the article to access the forum.)

  • Read the complete Diagnosing Java code series by Eric Allen.

  • For a detailed analysis of the many problems with extending Java generics to include mixins, as well as proposed solutions, see "A First-Class Approach to Genericity" (PDF) by Allen, Bannet, and Cartwright.

  • Be sure to read the transcript of "Classes and Mixins" by Matthew Flatt, Shriram Krishnamurthi, Matthias Felleisen from the conference record of POPL 98: The 25TH ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages for more details on their mixin solution.

  • Learn how the practice of including extraneous zeroary constructors can cause problems in the author's April 2002 column, "The Run-on Initializer bug pattern."

  • Get a jump on generics in Java by downloading the JSR-14 prototype compiler; it includes the sources for a prototype compiler written in the extended language, a JAR file containing the class files for running and bootstrapping the compiler, and a JAR file containing stubs for the collection classes.

  • You can download the NextGen prototype compiler right now.

  • Check out DrJava, a free Java IDE that supports interactive evaluation of Java statements and expressions, and supports generic Java syntax and compilation.

  • Follow the discussion of adding generic types to Java by reading the Java Community Process proposal, JSR-14.

  • Keith Turner offers another look at this topic with his article "Catching more errors at compile time with Generic Java" (developerWorks, March 2001).

  • This paper, "Automatic Code Generation from Design Patterns" (PDF), from IBM Research, describes the architecture and implementation of a tool that automates the implementation of design patterns.

  • Find hundreds more Java technology resources on the developerWorks Java technology zone.

About the author
Eric Allen sports a broad range of hands-on knowledge of technology and the computer industry. With a B.S. in computer science and mathematics from Cornell University and an M.S. in computer science from Rice University, Eric is currently a Ph.D. candidate in the Java programming languages team at Rice. Eric is a project manager for and a founding member of the DrJava project, an open-source Java IDE designed for beginners; he is also the lead developer of the university's experimental compiler for the NextGen programming language, an extension of the Java language with added experimental features. Contact Eric at eallen@cs.rice.edu.


Discuss71 KBe-mail it!

What do you think of this document?
Killer! (5) Good stuff (4) So-so; not bad (3) Needs work (2) Lame! (1)

Send us your comments or click Discuss to share your comments with others.



developerWorks > Java technology
developerWorks
  About IBM  |  Privacy  |  Terms of use  |  Contact