Skip to main content

Diagnosing Java code: Java generics without the pain, Part 4

How generic types can conquer mischievous mixins

Eric Allen (eallen@cs.rice.edu), Ph.D. candidate, Java programming languages team, Rice University
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.

Summary:  Java 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.

View more content in this series

Date:  13 May 2003
Level:  Intermediate
Activity:  2395 views

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

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

  • 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.

  • 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.

  • 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.

Comments (Undergoing maintenance)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology
ArticleID=10810
ArticleTitle=Diagnosing Java code: Java generics without the pain, Part 4
publish-date=05132003
author1-email=eallen@cs.rice.edu
author1-email-cc=

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Special offers