3

I am searching for a way to build a wrapper for multiple enums. Say you have

public enum Enum1 {
    A,B,C
}

public enum Enum2 {
    ONE,TWO,THREE
}

I want to have a new enum with the literals

(A,ONE), (A,TWO), (A,THREE), (B,ONE), ...

The whole thing generic so that I don't have to know Enum1 and Enum2. Is there a way to build that or even extend it to n Enums?

Or should I look to other general ways to model that?

ratatosk
  • 373
  • 5
  • 19
  • Unless you're looking for a code generator, I don't think "generic enum" makes much sense, as enums are quite explicit... – Puce Nov 28 '12 at 16:48
  • what I mean with generic Enum is a generic class that need something, that extends Enum. Like class Analyser> – ratatosk Nov 28 '12 at 16:50
  • OK, then maybe you should rephrase: "I want to have a new enum with the literals" – Puce Nov 28 '12 at 16:53

4 Answers4

5

I would set up a class to represent the tuple you are referring to. You could use generics here, if you want:

public class EnumTupleImpl<E1 extends Enum<E1>, E2 extends Enum<E2>> {
    private final E1 e1;
    private final E2 e2;

    public EnumTupleImpl(E1 e1, E2 e2) {
        this.e1 = e1;
        this.e2 = e2;
    }

    public E1 getE1() {
        return e1;
    }

    public E2 getE2() {
        return e2;
    }
}

Usage:

EnumTupleImpl<Enum1, Enum2> tupe1 = new EnumTupleImpl<Enum1, Enum2>(Enum1.A, Enum2.ONE);
EnumTupleImpl<Enum1, Enum2> tupe2 = new EnumTupleImpl<Enum1, Enum2>(Enum1.A, Enum2.TWO);
EnumTupleImpl<Enum1, Enum2> tupe3 = new EnumTupleImpl<Enum1, Enum2>(Enum1.A, Enum2.THREE);

You could also represent each tuple by a enum instance, like so:

public enum Tuple {
    AONE(Enum1.A, Enum2.ONE),
    ATWO(Enum1.A, Enum2.TWO),
    ATHREE(Enum1.A, Enum2.THREE);

    private Enum1 e1;
    private Enum2 e2;

    private EnumTupleEnum(Enum1 e1, Enum2 e2) {
        this.e1 = e1;
        this.e2 = e2;
    }

    public Enum1 getE1() {
        return e1;
    }

    public Enum2 getE2() {
        return e2;
    }
}

usage:

Tuple.AONE.getE1(); //returns A
Tuple.AONE.getE2(); //returns ONE

What may make sense for you would be to use a generic interface to wrap both the enum representation and class representation like so:

public interface EnumTuple<E1, E2> {
    E1 getE1();
    E2 getE2();
}

which would allow you to use the class or enum interchangeably:

public class EnumTupleImpl<E1 extends Enum<E1>, E2 extends Enum<E2>> imlements EnumTyple<E1, E2>{
...
}

public enum Tuple implements EnumTuple<Enum1, Enum2>{
...
}

usage:

EnumTuple<Enum1, Enum2> tupe1 = new EnumTupleImpl<Enum1, Enum2>(Enum1.A, Enum2.ONE);
EnumTuple<Enum1, Enum2> enum1 = Tuple.AONE;
John Ericksen
  • 10,995
  • 4
  • 45
  • 75
  • Hmmm, you may be able to represent each tuple by another enum instance, if that's what you want. You may not be able to use generics though. – John Ericksen Nov 28 '12 at 16:52
2

In addition to @johncarl's answer, you can try:

Class<E1> enumType1 =...
Class<E2> enumType2 = ...

List<EnumTuple<E1, E2>> enumTuples = new ArrayList<>(enumType1.getEnumConstants().length * enumType2.getEnumConstants().length);

//or

Set<EnumTuple<E1, E2>> enumTuples = new HashSet<>(enumType1.getEnumConstants().length * enumType2.getEnumConstants().length);

for (E1 e1 : enumType1.getEnumConstants()){
    for (E2 e2 : enumType2.getEnumConstants()){
        enumTuples.add(new EnumTuple<>(e1, e2));
    }
}

Don't forget to implement equals and hashCode() if you're using HashSet.

Puce
  • 37,247
  • 13
  • 80
  • 152
  • This solution is great, thank you. But I think, there is no simple way to an arbitrary number of enums. – ratatosk Nov 28 '12 at 19:45
  • 1
    Have a look for the typesafe heterogeneous container design pattern (see e.g. the book Effective Java 2nd Edition by Joshua Bloch) – Puce Nov 28 '12 at 22:28
2

And here's one that will wrap any number of enums. It comes as an Iterator but combine it with either or both of the other solutions and I think you've got all you asked for.

public class Test {
  public static void main(String args[]) {
    new Test().test();
  }

  public static class EnumIterator implements Iterator<Enum[]> {
    // The enums
    private final Enum[][] enums;
    // Where we are in each column.
    private final int[] is;
    // Which column to increment next.
    private int i = 0;

    // Construct from Enum[]s.
    public EnumIterator(Enum[]... enums) {
      // Grab the enums.
      this.enums = enums;
      // Start all ordinals at zero.
      is = new int[enums.length];
      // Next one to increment is the last one.
      i = enums.length - 1;
    }

    // Construct from classes.
    public EnumIterator(Class<? extends Enum>... classes) {
      this(enumsFromClasses(classes));
    }

    // Factory to build the Enum[] array from an array of classes.
    private static Enum[][] enumsFromClasses(Class<? extends Enum>[] classes) {
      Enum[][] theEnums = new Enum[classes.length][];
      for ( int j = 0; j < classes.length; j++ ) {
        theEnums[j] = classes[j].getEnumConstants();
      }
      return theEnums;
    }

    @Override
    public boolean hasNext() {
      // We stop when we are about to increment col 0 and we are at its end.
      return (i > 0 || is[0] < enums[0].length);
    }

    @Override
    public Enum[] next() {
      if (hasNext()) {
        // One from each.
        Enum[] next = new Enum[enums.length];
        for (int j = 0; j < next.length; j++) {
          next[j] = enums[j][is[j]];
        }
        // Step - Kinda like incrementing a number with each digit in a different base.
        // Walk back past '9's setting them to 0.
        for (i = is.length - 1; i > 0 && is[i] == enums[i].length - 1; i--) {
          // Back one.
          is[i] = 0;
        }
        // Step that one up one.
        is[i] += 1;
        return next;
      } else {
        throw new NoSuchElementException();
      }
    }

    @Override
    public void remove() {
      throw new UnsupportedOperationException("Not supported.");
    }
  }

  enum ABC {
    A, B, C;
  }

  enum XY {
    X, Y;
  }

  enum IJ {
    I, J;
  }

  private void test() {
    // Also works - but constructing from classes is cleaner.
    //Iterator<Enum[]> i = new EnumIterator(ABC.values(), XY.values(), IJ.values());
    Iterator<Enum[]> i = new EnumIterator(ABC.class, XY.class, IJ.class);
    for (Enum[] e : Iterables.in(i)) {
      System.out.println(Arrays.toString(e));
    }
  }
}

Prints:

[A, X, I]
[A, X, J]
[A, Y, I]
[A, Y, J]
[B, X, I]
[B, X, J]
[B, Y, I]
[B, Y, J]
[C, X, I]
[C, X, J]
[C, Y, I]
[C, Y, J]

Note that Iterables.in merely wraps an Iterator<E> in an Iterable<E> like this (not my code - I found it here on SO).

public class Iterables {
  /**
   * Adapts an {@link Iterator} to an {@link Iterable} for use in enhanced for loops.
   *
   * If {@link Iterable#iterator()} is invoked more than once, an
   * {@link IllegalStateException} is thrown.
   */
  public static <T> java.lang.Iterable<T> in(final Iterator<T> iterator) {
    assert iterator != null;
    class SingleUseIterable implements java.lang.Iterable<T> {
      private boolean used = false;

      @Override
      public Iterator<T> iterator() {
        if (used) {
          throw new IllegalStateException("SingleUseIterable already invoked");
        }
        used = true;
        return iterator;
      }
    }
    return new SingleUseIterable();
  }
}
Community
  • 1
  • 1
OldCurmudgeon
  • 64,482
  • 16
  • 119
  • 213
0

I needed to do this for Parameterized test recently. My preferred approach is with Java8 streams and flatmap:

    enum Color {
        RED,
        BLUE;
    }

    enum Shape {
        CIRCLE,
        SQUARE;
    }

    enum Size {
        BIG,
        SMALL;
    }

    public static Stream<Object[]> data() {
        return Arrays.stream(Color.values()).flatMap(color -> {
            return Arrays.stream(Shape.values()).flatMap(shape -> {
                return Arrays.stream(Size.values()).map(size -> new Object[] {color, shape, size});
            });
        });
    }

You can always call .toArray() on the final stream if you need.

Dan Fox
  • 1,643
  • 15
  • 10