【问题标题】:Elegant generic "Cross product" of enums in javajava中枚举的优雅通用“交叉产品”
【发布时间】:2012-11-16 14:27:00
【问题描述】:

我正在寻找一种方法来为多个枚举构建包装器。说你有

public enum Enum1 {
    A,B,C
}

public enum Enum2 {
    ONE,TWO,THREE
}

我想要一个带有文字的新枚举

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

整个事情都是通用的,所以我不必知道 Enum1 和 Enum2。有没有办法构建它甚至将它扩展到 n 个枚举?

或者我应该寻找其他通用方法来建模吗?

【问题讨论】:

  • 除非您正在寻找代码生成器,否则我认为“通用枚举”没有多大意义,因为枚举非常明确......
  • 我所说的泛型 Enum 是一个泛型类,它需要一些东西,它扩展了 Enum。像 class Analyser>
  • 好的,那么也许你应该改写:“我想要一个带有文字的新枚举”

标签: java generics enums


【解决方案1】:

我最近需要为参数化测试执行此操作。我首选的方法是使用 Java8 流和平面图:

    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});
            });
        });
    }

如果需要,您可以随时在最终流中调用.toArray()

【讨论】:

    【解决方案2】:

    这里有一个可以包装任意数量的枚举。它以迭代器的形式出现,但将它与其他解决方案中的一个或两个结合起来,我认为你已经得到了你想要的一切。

    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));
        }
      }
    }
    

    打印:

    [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]
    

    请注意,Iterables.in 只是像这样将 Iterator&lt;E&gt; 包装在 Iterable&lt;E&gt; 中(不是我的代码 - 我在 SO 上找到了 here)。

    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();
      }
    }
    

    【讨论】:

      【解决方案3】:

      除了@johncarl的回答,你可以试试:

      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));
          }
      }
      

      如果您使用 HashSet,请不要忘记实现 equals 和 hashCode()。

      【讨论】:

      • 这个解决方案很棒,谢谢。但我认为,对于任意数量的枚举没有简单的方法。
      • 查看类型安全的异构容器设计模式(参见 Joshua Bloch 的《Effective Java 2nd Edition》一书)
      【解决方案4】:

      我会设置一个类来表示您所指的元组。如果你愿意,你可以在这里使用泛型:

      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;
          }
      }
      

      用法:

      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);
      

      你也可以用一个枚举实例来表示每个元组,像这样:

      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;
          }
      }
      

      用法:

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

      对您来说可能有意义的是使用通用接口来包装枚举表示和类表示,如下所示:

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

      这将允许您互换使用类或枚举:

      public class EnumTupleImpl<E1 extends Enum<E1>, E2 extends Enum<E2>> imlements EnumTyple<E1, E2>{
      ...
      }
      
      public enum Tuple implements EnumTuple<Enum1, Enum2>{
      ...
      }
      

      用法:

      EnumTuple<Enum1, Enum2> tupe1 = new EnumTupleImpl<Enum1, Enum2>(Enum1.A, Enum2.ONE);
      EnumTuple<Enum1, Enum2> enum1 = Tuple.AONE;
      

      【讨论】:

      • 嗯,如果你想要的话,你可以用另一个枚举实例来表示每个元组。不过,您可能无法使用泛型。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-01-22
      • 2013-04-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-02-01
      相关资源
      最近更新 更多