15

I have a generic class which represents a fragment of text. That fragment of text may have any of a number of different modes (different types of highlighting). Those modes are represented by an Enum. The Enum could be different for each project but it must implement an interface which provides a method to combine 2 of them (could be highlighted and bolded). So i have an interface:

public interface TextFragmentMode<E extends Enum<E>> {
    /**
     * Will combine the supplied mode with the current mode and return the
     * result.
     * 
     * @param mode The mode to combine with.
     * @return The combined mode.
     */
    public E combine( E mode );
}

Then my TextFragment is a container for both a String of text, and a mode. But when I try to declare the class:

public class TextFragment<E extends TextFragmentMode<E extends Enum<E>>> {
    StringBuilder text;
    E mode;
    ...

I get the following error:

Syntax error on token "extends", , expected

Which, according to eclipse syntax highlighting, is referring to the

E extends Enum<E>

portion of the code. Does anyone know what I am doing wrong? I must be missing something about Generics...

--------------------- edit -------------------

I'm finally taking the time to read Effective Java by Josh Bloch (second edition), and it turns out he goes over this use case as Item 34: Emulate extensible enums with interfaces. As much as I would like to say great mind think alike... That would be WAY too presumtuous!

Lucas
  • 14,227
  • 9
  • 74
  • 124

4 Answers4

16

TextFragment<E> needs to say two things about E.

  • It "extends" TextFragmentMode<E>.
  • In order to do that, you must also constrain it to extend Enum<E>.

Because of Java inheritance wonkiness, you need to write that the other way around:

public class TextFragment<E extends Enum<E> & TextFragmentMode<E>> {
Tom Hawtin - tackline
  • 145,806
  • 30
  • 211
  • 305
  • 1
    Yes, this works. Thank you much! Turns out its called an Intersection Type in the JLS 3rd edition. Would have been nice if the Java generics tutorial even mentioned that there could be more advanced usage scenarios. For those who find this question, there is more useful information here: http://java.sun.com/docs/books/jls/third_edition/html/typesValues.html and here: http://stackoverflow.com/questions/574529/coding-tip-intersection-types-and-java-enums Again, thank you. – Lucas Nov 23 '10 at 18:07
7

The problem is that you're trying to make E extend TextFragmentMode and Enum, which aren't related types. What type E would satisfy both constraints?

I suspect you want two type parameters, something like this:

public class TextFragment<E extends Enum<E>, M extends TextFragmentMode<E>>

Now you have each constraint expressed on a different type parameter, and they both make sense - you can definitely find an E which is an enum, and an M which is a TextFragmentMode<E>. However, it's pretty complicated...

... do you definitely need it to be this generic? What will you be doing with M in the class? Could you not just take a TextFragmentMode<E> as a constructor parameter (or whatever) and make it generic in one type parameter again?

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Ok, so your result line of code is one of the solutions I arrived at, but it makes everything ugly. Then every time you declare/instantiate you need both type parameters and they themselves are a little long. You end up using 80 characters on the name of the type. And in practice, my implementation type was: public enum MyType implements TextFragmentMode which is both an enum and a TextFragmentMode... – Lucas Nov 23 '10 at 17:45
1

You need to introduce a new type that accounts for the bound of Enum

public class TextFragment<T extends Enum<T>, E extends TextFragmentMode<T>> {
John Vint
  • 39,695
  • 7
  • 78
  • 108
  • Again (per comment above) I dont really like this (though it would work) because of the inelegance. Its very ugly to look at and it seems like there should be a better way to do it. – Lucas Nov 23 '10 at 17:47
-2

Without testing, I'd guess:

public class TextFragment<E extends TextFragmentMode<E>> {

Hmm, short test shows it doesn't seem to work either...

Puce
  • 37,247
  • 13
  • 80
  • 152