0

I have in mind an interface which declares a method for converting a raw score from a survey to a percentile value for a particular category, where the categories are described by more than one otherwise unrelated enum types. So I started with this:

public interface NormTable<E extends Enum<E>> {
    Integer percentileForRawScore(E e, int score);
}

I've then got an implementing class for, say, the Foo survey and its Subscale enum:

public class FooSubscaleNormTable implements NormTable<Subscale> {
    public Integer percentileForRawScore(Subscale e, int score) {
        // Implementation
    }
}

And finally, a factory class for creating the appropriate concrete class:

public class NormTableFactory {
    public static <E extends Enum<E>> NormTable<E> normTableForSurveyAndType(
        String surveyCode, Class<E> clazz) {
    if ("Foo".equals(surveyCode) && Subscale.class.equals(clazz)) {
        return (NormTable<E>) new FooSubscaleNormTable();
    } else {
            // ...
        }
    }
}

I suspect the cast there is wrong, as Eclipse flags it as unchecked, though lets me carry on. The Java 1.6 compiler on the build server, however, is not happy at all and flags them as inconvertible types.

Have I got the signatures in the factory class wrong, or is my whole architecture looking suspect? I note above that the enum types are "otherwise unrelated", though they all represent categories within surveys, and could conceivably implement an interface to unify their types. Would that help?

Paul A. Hoadley
  • 1,518
  • 1
  • 13
  • 17

1 Answers1

2

You shouldn't use a concrete Enum class in your declaring type.

Make FooSubscaleNormTable generic as well

public class FooSubscaleNormTable<E extends Enum<E>> implements NormTable<E> {
    public Integer percentileForRawScore(E e, int score) {
       if(e instanceof Subscale) {
           switch((Subscale) e) {
           case X:
           // do something
           break;
           }
       }
       // return 0; 
    }
}

and change your factory to

public class NormTableFactory {
    public static <E extends Enum<E>> NormTable<E> normTableForSurveyAndType(
        String surveyCode, Class<E> clazz) {
    if ("Foo".equals(surveyCode) && Subscale.class.equals(clazz)) {
        return (NormTable<E>) new FooSubscaleNormTable<E>();
    } else {
            // whatever else
        }
    }
}

Then when you invoke it, that is when you pass in your desired enum type.

NormTable<Subscale> scale = NormTableFactory.normTableForSurveyAndType("surveyCode", Subscale.class);
Jeshurun
  • 22,940
  • 6
  • 79
  • 92
  • Thanks for that, I'm almost there now. In making `FooSubscaleNormTable` generic, though, I seem to have lost the ability to refer to a particular enum type. That class, for example, takes a `Subscale` enum, and I need to be able to determine its value in `percentileForRawScore()`, something like this: `if (e == Subscale.Bar) { // ... }`. Again, Eclipse is not flagging this, but the OpenJDK 1.6 compiler on the build server is not happy: `incomparable types: E and Subscale.Bar`. – Paul A. Hoadley May 27 '12 at 08:23
  • The nice thing about enums is that you can use them in a switch statement. Try something like `switch(e) {case Bar: some statement; break; case ... }` – Jeshurun May 27 '12 at 08:28
  • I'm getting `Cannot switch on a value of type E...` from Eclipse, and yet `E extends Enum` is in the class declaration. I'm a bit stumped. I may need to start this from scratch. – Paul A. Hoadley May 27 '12 at 09:00
  • 1
    Thats right, because at compile time, the compiler will have no idea what the type of generic enum will be. If `percentileForRawScore` will take several types of enums as arguments, you will have to do an instance check and a cast. If it will always only accept a `Subscale`, you do not need generics at all, you can simply change the method to `percentileForRawScore(Subscale s, int score)` and remore the instancecheck and cast, and also remove the generic type from the class. – Jeshurun May 27 '12 at 09:16
  • Believe it or not, I couldn't cast either: `inconvertible types`. The solution was using `e.equals(Subscale.Bar)` rather than `e == Subscale.Bar`, and back to the if-statements rather than a switch. http://stackoverflow.com/questions/8087337/java-generics-code-compiles-in-eclipse-but-not-in-command-line – Paul A. Hoadley May 27 '12 at 09:55