Musings on JDK development

Mixing-in an Enum

Effective Java discusses two variants of the type-safe enum pattern, one that allows subclassing and one that does not. The enum language construct added in JDK 5 only provides the non-subclassing variant (because supporting subclassing would have confusing interactions with switch statements and other enum features). However, having the enum class implement a mixin interface can restore some of the third-party extensibility of the subclassing variant. (In Java, a mixin interface is used to capture some secondary behavior a class can provide; in other languages, mixins can carry implementations too.)

During JSR 199, Peter, Jon, and I were having a meeting discussing a few API design issues. The JavaFileMangager has a notion of locations of where to look for files. While there are a number of standard locations that needed to be supported for a Java compiler, such as classpath and sourcepath, other kinds of tools need to be able to define their own locations too. So using an enum for the known compiler locations would be structured, but not support the needed extensibility while using a list of known strings to lookup locations would allow extensions, but be very unstructured. The solution we came up with was to use an enum and a name-based lookup.

The solution has a few parts:

  1. Define a simple interface with the needed functionality.
    In this case, the JavaFileManager.Location interface just has two methods, getName and isOutputLocation. The first is used to retrieve the key of the location and the second is used by the file manager.

  2. Declare an enum implementing the interface where the enum constants represent the known values. For example, StandardLocation
    implements JavaFileManager.Location and has constants for the classpath, sourcepath, output directory, and other locations already in use by javac.
    Since enum types are classes, they can implement interfaces.

  3. Include a factory method mapping from names to objects implementing the interface. For JSR 199, The locationFor
    method maps from strings to locations. A factory method for the interface, unlike the valueOf method on the enum, is able to support aliasing of names and return objects from types that implement the interface other than the enum. That is, it would be possible for the factory method to map multiple names to the same object rather than just one canonical name. Including a factory isn't strictly necessary, but is would be a bit onerous to force clients to write their own factory or use anonymous classes, etc. to create non-standard values.

This combination of a enum for known values and an interface for extensibility provides a good alternative to string-based provider lookups.

Join the discussion

Comments ( 5 )
  • Howard Lovatt Wednesday, May 16, 2007
    This is a nice solution, but I am going to be deliberately provocative to try and learn something about why you chose the particular solution that you did. Instead of the factory, why not provide a public static final map that maps from string to location and to which others can add new mappings?


    public static final Map< String, Location > locations = new HashMap< String, Location >();
    static {
    locations.put( CLASSPATH.getName(), CLASSPATH );
  • Joe Darcy Wednesday, May 16, 2007
    Providing a mutable map like that would be a bit tedious to get to behave as intended and would be a potential source of a memory leak. By default doing put on a map will replace an existing value, which presumably should be disallowed at least for the built-in mappings. Those mappings also shouldn't be removable. The lifetime of all of the location objects should not necessarily be the same as the lifetime of the Map (forever!) and expecting clients to remove a location explicitly would negate the benefits of garbage collection.
  • Howard Lovatt Thursday, May 17, 2007
    Thanks for the reply. I am been provocative and not suggesting that you have made a mistake in your design, I have a similar problem that I can't get to the bottom of and hence the questions.

    If you have one factory that is fine. You check the super factory, the built in one, and then check for other locations locally. The problem arises when you have two factors that are unrelated - perhaps from two separate third parties.

    Now you have no one place to look for locations, you have to check both factories.

  • Debasish Ghosh Thursday, May 17, 2007
    I understand why the Map is not public as Howard was provocating. Does the current design address the memory leak problem ? Anyway you have the mutable map and as u have suggested, lifetime of all of the location objects should not necessarily be the same as the lifetime of the Map. Shouldn't we have references here ? Maybe I am missing something.
  • Joe Darcy Monday, May 21, 2007

    Let's see,

    As currently written and specified, the implicit map backing locationFor will retain objects. Enums are defined to use identity for equality; that is, .equals is ==. So for other implementations of the interface to interoperate with the enum constants in collections, the other implementations will have to use identity for equality too. A variant of the pattern could require consumers of the objects to rely more on aspects of the objects other than their identity and thereby not necessarily have a persistent map backing the factory.

    Some parts of JSR 199 (and 269) API are named at other service providers other than users. So, if I were to write JoesOwnJavaFileManager I could also provide an enum of locations my file manager recognized in addition to the standard ones.

Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.