How to extend enum in Java

By | September 28, 2013

In Java feature, it is not possible to have one enum extend another. For most of cases you may encounter, it is reasonable: not only would it be confusing whether to enumerate over all of the elements of a superclass and its subclass, but also make difficult to interact with switch statements.

But occasionally, you may still want to make an enum class extensible. For example, you need an enum to represent basic fonts supported by your system and want third parties to be able to add new fonts. The solution we came up with was to use an enum and a name-based lookup. Both will be done by implementing a mixin interface.

In Java, a mixin is an interface which contains a combination of methods from other classes. It encourages code reuse and avoid multiple inheritance as well. This post will suggest one way to restore the subclassing variant using mixin. The solution has a few parts (copied from “Mixing-in an Enum”):

  • Define an interface with the needed functionality.
  • Declare enums implementing the interface.
  • Include a factory method mapping from names to objects implementing the interface.

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

An example

Let’s begin with a simple example. There are a number of basic monospaced fonts supported for an Ubuntu system, such as “Courier”, “Consolas”, and “DejaVu”, so using an enum for the default installed fonts would be structured. For example, BasicMonoFont is created and has constants for the “Courier”, “Consolas”, and “DejaVu”.

public enum BasicMonoFont {
  Courier("Courier", new File("/usr/share/fonts")), //
  Consolas("Consolas", new File("/usr/share/fonts")), //
  DejaVu("DejaVu", new File("/usr/share/fonts"));

  private final String fontName;
  private final File   location;

  private BasicMonoFont(String fontName, File location) {
    this.fontName = fontName;
    this.location = location;
  }

  public String getFontName() {
    return fontName;
  }

  public File getLocation() {
    return location;
  }
}

But we need to add other fonts too, such as “Lucida”, “Monaco”, and “Andale”. We can, of course, change the source code to add new fonts, but it absolutely lacks of extensibility. The solution we came up with was to use an enum and a name-based lookup. Both will be done by implementing a mixin interface.

1. Mixin interface

In Java, a mixin is an interface which contains a combination of methods from other classes. It encourages code reuse and avoid multiple inheritance as well. To solve the problem above, we first define a interface with needed operations. In this case, the MonoFont has two methods, getFontName() and getLocation(). The first is used to retrieve the key of the font and the second is used to store its installed location.

public interface MonoFont {
  public String getFontName();
  public File getLocation();
}

After that we modify the above enum by implementing the interface. Note that, we almost change nothing for class BasicMonoFont.

public enum BasicMonoFont implements MonoFont {
  Courier("Courier", new File("/usr/share/fonts")), //
  Consolas("Consolas", new File("/usr/share/fonts")), //
  DejaVu("DejaVu", new File("/usr/share/fonts")), ;

  private final String fontName;
  private final File   location;

  private BasicMonoFont(String fontName, File location) {
    this.fontName = fontName;
    this.location = location;
  }

  @Override
  public String getFontName() {
    return fontName;
  }

  @Override
  public File getLocation() {
    return location;
  }
}

Finally and most importantly, we include a factor method monoFontFor() in BasicMonoFont, mapping from names to fonts implementing MonoFont. monoFontFor() is different from Enum.valueOf() method because it can return objects of MonoFont other than the enum. Also it can map multiple names to the same object rather than just one canonical name.

public enum BasicMonoFont implements MonoFont {
  ...
  private static Map< String, MonoFont > map = 
      new TreeMap < String, MonoFont > ();
  /*
   * Thanks for Tomáš Záluský to point it out this bug.
   * In my previous code, if one calls addNewFont before first call of 
   * monoFontFor, then lazy initialization won’t proceed and basic 
   * constants won’t go into map
   */
  static {
    for (MonoFont font : values()) {
      map.put(font.getFontName(), font);
    }
  }

  public static MonoFont monoFontFor(String fontName) {
//    if (map.isEmpty()) {
//      for (MonoFont font : values()) {
//        map.put(font.getFontName(), font);
//      }
//    }
    return map.get(fontName);
  }
  
  public static void addNewFont(MonoFont font) {
    if (!map.containsKey(font.getFontName())) {
      map.put(font.getFontName(), font);
    }
  }
}

2. Add new elements

For new fonts, there are at least two ways of adding them. One is to create anonymous classes, for example,

BasicMonoFont.addNewFont(new MonoFont() {

  @Override
  public String getFontName() {
    return "Andale";
  }

  @Override
  public File getLocation() {
    return new File("~/.fonts");
  }
});

The other is to write another enum

public enum ExMonoFont implements MonoFont {

  Lucida("Lucida", new File("~/.fonts")), //
  Monaco("Monaco", new File("~/.fonts")), //
  Andale("Andale", new File("~/.fonts"));

  private final String fontName;
  private final File   location;

  private ExtendedFont(String fontName, File location) {
    this.fontName = fontName;
    this.location = location;
  }

  @Override
  public String getFontName() {
    return fontName;
  }

  @Override
  public File getLocation() {
    return location;
  }
}

and all of elements.

BasicMonoFont.addNewFont(ExMonoFont.Andale);
BasicMonoFont.addNewFont(ExMonoFont.Lucida);
BasicMonoFont.addNewFont(ExMonoFont.Monaco);

3. Looking up fonts

After adding new fonts (or not), looking-up fonts is straightforward

MonoFont font = BasicMonoFont.monoFontFor("Andale");

Other thoughts

Some thoughts to extend the mix-in stradegy.

  1. Add a static method values() in BasicMonoFont to return a collection containing all “added” fonts. This will be very helpful to iterate all fonts.

    public static Collection<MonoFont> fonts() {
    return map.values();
    }
    
  2. Create another class FontManager and move all static methods in the manager.

13 thoughts on “How to extend enum in Java

  1. Tomáš Záluský

    Thanks for interesting article. Small notes:

    1. With this approach you have to precisely define and document what to do if real enum constant conflicts with fake constant. Any policy of real-beats-fake, fake-beats-real, throw-exception may have sense and also none of them is noticeably more intuitive than remaining ones.
    2. In implementation stated above, if one calls addNewFont before first call of monoFontFor, then lazy initialization won’t proceed and real constants won’t go into map unless added manually. Little surprising.

    I personally observed if I need to extend enum, it should probably not be implemented as enum. Switching humbly to 1.4-old-style public static final constants often suffices. Creation can be more convenient with factory methods. Usage in switch statement is often antipattern where polymorhism or enum visitor are usually more flexible alternative.

    Reply
    1. Yifan Peng Post author

      Thanks for your comments.

      1. In implementation stated above, if one calls addNewFont before first call of monoFontFor, then lazy initialization won’t proceed and real constants won’t go into map unless added manually. Little surprising.
      

      This is definitely a bug in my code. I should add a static block to initialize the map; otherwise, as you pointed out, if addNewFont is invoked first, no basic font will be added in the map.

      I personally observed if I need to extend enum, it should probably not be implemented as enum.*
      

      In the book of “Effective Java (1st edition)”, two variants of enum pattern have been discussed, one that allows subclassing and one that does not. JDK only provides the non-subclassing variant. In the “Effective Java (2nd edition)”, he further states “In almost all respects, enum types are superior to the typesafe enum pattern described in the first edition of this book [Bloch01]. On the face of it, one exception concerns extensibility, which was possible under the original pattern but is not supported by the language construct. In other words, using the pattern, it was possible to have one enumerated type extend another; using the language feature, it is not.” He also provides another way for extensible enumerated types, which is operation codes, also known as opcodes. Maybe you are interested in reading this part.

      From my experience, I prefer using enum whenever possible, which means I try best to stick to it. Of course, sometimes, I have to compromise by using “static final constants”.

      Reply
  2. Jose

    Returned values are implementations of MonoFont not enums at all. You’re not extending an enum here just creating an interface backed by a map that can hold as many items as required. There’s no difference using a regular class at all

    Reply
    1. Yifan Peng Post author

      Directly extending an enum is next to impossible because of the limitation of Java features. This post only provides a way to restore some of the third-party extensibility. The main difference between mix-in interface and regular classes is that you can use BasicMonoFont and ExMonoFont as normal enums (which means you can use it in switch and with “==” operator), and at the same time you can use the interface to iterate all their elements.

      Reply
  3. how to cancel google plus account

    Do you mind if I quote a couple of your articles
    as long as I provide credit and sources back to your blog?
    My blog is in the exact same area of interest as yours and my visitors
    would genuinely benefit from a lot of the information you provide here.

    Please let me know if this okay with you. Appreciate it!

    Reply
  4. YOLO

    Can I just say what a relief to discover an individual who genuinely knows what they are discussing on
    the net. You definitely realize how to bring an issue to
    light and make it important. A lot more people have to check this out and understand this
    side of your story. I was surprised that you aren’t
    more popular since you surely have the gift.

    Reply
  5. Shan He

    Instead of thinking of it as extending an enum, may be it makes more sense to have a Map, whose entries can be loaded from an enum. or this Map can be constructed from multiple enums. In short, it is extended by enums, not extending enums. I do not think this is a mixin, either.

    Reply
  6. Srikanth Chakravarthy

    After trying a while about how to collect enums dynamically and failing to arrive at a feasible solution, I found your write-up and it does just what I wanted to achieve. It is very well written and easy to try out. Thanks a lot for this article.

    Reply
  7. Pingback: Inheritance of Enums – Best Java Answer

  8. Johnny DeLone

    This adds all enums to a map, which then resides in memory. If you have a lot of enums, doesn’t this pose a potential problem?

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *