Search code examples
javaenumsabstractionjava-6code-duplication

Java enums: implementing common methods, avoiding duplication


Let's say that I have two differents enumeration

public enum SomeEnumClass {

    private static  final SomeEnumClass[]   mValues = SomeEnumClass .values();
    ONE(1), TWO(2), THREE(3);

}

public enum OtherEnumClass {
    private static  final OtherEnumClass[]  mValues = OtherEnumClass .values();
    Monday(1), Tuesday(2), Wednesday(3), Thrusday(4), Friday(5), Saturday(6), Sunday(7)

}

The enum have in common the type of data they carry (here, an int), and differ in their name and number of possible values.

For each of those enum, I have several method to implements, which are strictly identical. Example:

        public static OtherEnumClass getCell(int index)
    {
        if (index < OtherEnumClass .mValues.length )
        {
            return OtherEnumClass .mValues[index];              
        }
        throw new IllegalArgumentException("Invalid " + OtherEnumClass .class.getSimpleName() + " value: " + index);
    }

I am trying to find a way to avoid duplication of those methods, like I would do with abstract classes. But so far I am coming with nothing.

We are using java 1.6 and cannot upgrade for now. Any help is appreciated. thanks.


Solution

  • Your code example is a bit misleading as it returns the constant with the same ordinal rather than the one with the same property value. In order to abstract the search for a constant with a property value, you have to abstract the property, e.g.

    interface TypeWithIntProperty {
      int getProperty();
    }
    enum Number implements TypeWithIntProperty {
      ONE(1), TWO(2), THREE(3);
    
      private final int value;
    
      Number(int value) {
        this.value=value;
      }
      public int getProperty() {
        return value;
      }
    }
    enum DayOfWeek implements TypeWithIntProperty {
      Monday(1), Tuesday(2), Wednesday(3), Thrusday(4), Friday(5), Saturday(6), Sunday(7);
    
      private final int value;
    
      DayOfWeek(int value) {
        this.value=value;
      }
      public int getProperty() {
        return value;
      }
    }
    
    public class Helper {
      public static <E extends Enum<E>&TypeWithIntProperty>
                    E getEnumItem(Class<E> type, int value) {
        for(E constant: type.getEnumConstants())
          if(value == constant.getProperty())
            return constant;
        throw new IllegalArgumentException("no constant with "+value+" in "+type);
      }
    }
    

     

    DayOfWeek day=Helper.getEnumItem(DayOfWeek.class, 7);
    Number no=Helper.getEnumItem(Number.class, 2);
    

    If the properties have different types, you can make the interface generic:

    interface TypeWithIntProperty<T> {
      T getProperty();
    }
    enum Number implements TypeWithIntProperty<String> {
      ONE, TWO, THREE;
    
      public String getProperty() {
        return name().toLowerCase();
      }
    }
    enum DayOfWeek implements TypeWithIntProperty<Integer> {
      Monday(1), Tuesday(2), Wednesday(3), Thrusday(4), Friday(5), Saturday(6), Sunday(7);
    
      private final int value;
    
      DayOfWeek(int value) {
        this.value=value;
      }
      public Integer getProperty() {
        return value;
      }
    }
    
    public class Helper {
      public static <E extends Enum<E>&TypeWithIntProperty<P>,P>
                      E getEnumItem(Class<E> type, P value) {
        for(E constant: type.getEnumConstants())
          if(value.equals(constant.getProperty()))
            return constant;
        throw new IllegalArgumentException("no constant with "+value+" in "+type);
      }
    }
    

     

    DayOfWeek day=Helper.getEnumItem(DayOfWeek.class, 7);
    Number no=Helper.getEnumItem(Number.class, "two");
    

    A cleaner, but more verbose (under Java 6) alternative is to separate the property abstraction from the type having the property:

    interface Property<T,V> {
      V get(T owner);
    }
    enum Number {
      ONE, TWO, THREE;
      static final Property<Number,String> NAME=new Property<Number,String>() {
        public String get(Number owner) { return owner.getName(); }
      };
    
      public String getName() {
        return name().toLowerCase();
      }
    }
    enum DayOfWeek {
      Monday(1), Tuesday(2), Wednesday(3), Thrusday(4), Friday(5), Saturday(6), Sunday(7);
      static final Property<DayOfWeek,Integer> INDEX=new Property<DayOfWeek,Integer>() {
        public Integer get(DayOfWeek owner) { return owner.getIndex(); }
      };
    
      private final int index;
    
      DayOfWeek(int value) {
        this.index=value;
      }
      public int getIndex() {
        return index;
      }
    }
    public class Helper {
      public static <E extends Enum<E>,P>
                      E getEnumItem(Class<E> type, Property<E,P> prop, P value) {
        for(E constant: type.getEnumConstants())
          if(value.equals(prop.get(constant)))
            return constant;
        throw new IllegalArgumentException("no constant with "+value+" in "+type);
      }
    }
    

     

    DayOfWeek day=Helper.getEnumItem(DayOfWeek.class, DayOfWeek.INDEX, 7);
    Number no=Helper.getEnumItem(Number.class, Number.NAME, "two");
    

    This would be much simpler in Java 8 where you can implement Property as DayOfWeek::getIndex or Number::getName instead of inner classes, on the other hand, since we don’t benefit from the single-method interface in Java 6, we can turn this into an advantage by using an abstract base class which can provide the functionality, now even with caching:

    abstract class Property<T extends Enum<T>,V> {
      final Class<T> type;
      final Map<V,T> map;
      Property(Class<T> type) {
        this.type=type;
        map=new HashMap<V, T>();
        for(T constant: type.getEnumConstants())
        {
          T old = map.put(get(constant), constant);
          if(old!=null)
            throw new IllegalStateException("values not unique: "+get(constant));
        }
      }
      abstract V get(T owner);
      T getConstant(V value) {
        T constant=map.get(value);
        if(constant==null)
          throw new IllegalArgumentException("no constant "+value+" in "+type);
        return constant;
      }
    }
    enum Number {
      ONE, TWO, THREE;
      static final Property<Number,String> NAME=new Property<Number,String>(Number.class) {
        public String get(Number owner) { return owner.getName(); }
      };
    
      public String getName() {
        return name().toLowerCase();
      }
    }
    enum DayOfWeek {
      Monday(1), Tuesday(2), Wednesday(3), Thrusday(4), Friday(5), Saturday(6), Sunday(7);
      static final Property<DayOfWeek,Integer> INDEX
                   =new Property<DayOfWeek,Integer>(DayOfWeek.class) {
        public Integer get(DayOfWeek owner) { return owner.getIndex(); }
      };
    
      private final int index;
    
      DayOfWeek(int value) {
        this.index=value;
      }
      public int getIndex() {
        return index;
      }
    }
    

     

    DayOfWeek day=DayOfWeek.INDEX.getConstant(7);
    Number no=Number.NAME.getConstant("two");