我想采取一个现有的enum,并添加更多的元素,如下所示:

enum A {a,b,c}

enum B extends A {d}

/*B is {a,b,c,d}*/

这在Java中可行吗?


当前回答

不,在Java中不能这样做。除此之外,d可能是A的一个实例(考虑到“extends”的正常思想),但只知道A的用户不会知道它——这就违背了枚举是一组已知值的观点。

如果你能告诉我们更多关于你想如何使用它,我们可能会建议其他解决方案。

其他回答

不,在Java中不能这样做。除此之外,d可能是A的一个实例(考虑到“extends”的正常思想),但只知道A的用户不会知道它——这就违背了枚举是一组已知值的观点。

如果你能告诉我们更多关于你想如何使用它,我们可能会建议其他解决方案。

我的编码方式如下:

// enum A { a, b, c }
static final Set<Short> enumA = new LinkedHashSet<>(Arrays.asList(new Short[]{'a','b','c'}));

// enum B extends A { d }
static final Set<Short> enumB = new LinkedHashSet<>(enumA);
static {
    enumB.add((short) 'd');
    // If you have to add more elements:
    // enumB.addAll(Arrays.asList(new Short[]{ 'e', 'f', 'g', '♯', '♭' }));
}

LinkedHashSet既提供了每个条目只存在一次,又提供了它们的顺序保留。如果顺序无关紧要,则可以使用HashSet。以下代码在Java中是不可行的:

for (A a : B.values()) { // enum B extends A { d }
    switch (a) {
        case a:
        case b:
        case c:
            System.out.println("Value is: " + a.toString());
        break;
        default:
            throw new IllegalStateException("This should never happen.");
    }
}

代码可以这样写:

for (Short a : enumB) {
    switch (a) {
        case 'a':
        case 'b':
        case 'c':
            System.out.println("Value is: " + new String(Character.toChars(a)));
        break;
        default:
            throw new IllegalStateException("This should never happen.");
    }
}

从Java 7开始,你甚至可以用String做同样的事情:

// enum A { BACKWARDS, FOREWARDS, STANDING }
static final Set<String> enumA = new LinkedHashSet<>(Arrays.asList(new String[] {
        "BACKWARDS", "FOREWARDS", "STANDING" }));

// enum B extends A { JUMP }
static final Set<String> enumB = new LinkedHashSet<>(enumA);
static {
    enumB.add("JUMP");
}

使用enum替换:

for (String a : enumB) {
    switch (a) {
        case "BACKWARDS":
        case "FOREWARDS":
        case "STANDING":
            System.out.println("Value is: " + a);
        break;
        default:
            throw new IllegalStateException("This should never happen.");
    }
}

我希望我的一个同事的这个优雅的解决方案能在这篇长文章中看到,我想分享这种继承接口方法的方法。

请注意,我们在这里使用自定义异常,除非您将其替换为您的异常,否则此代码将无法编译。

文档内容很广泛,我希望大多数人都能理解。

每个子类枚举都需要实现的接口。

public interface Parameter {
  /**
   * Retrieve the parameters name.
   *
   * @return the name of the parameter
   */
  String getName();

  /**
   * Retrieve the parameters type.
   *
   * @return the {@link Class} according to the type of the parameter
   */
  Class<?> getType();

  /**
   * Matches the given string with this parameters value pattern (if applicable). This helps to find
   * out if the given string is a syntactically valid candidate for this parameters value.
   *
   * @param valueStr <i>optional</i> - the string to check for
   * @return <code>true</code> in case this parameter has no pattern defined or the given string
   *         matches the defined one, <code>false</code> in case <code>valueStr</code> is
   *         <code>null</code> or an existing pattern is not matched
   */
  boolean match(final String valueStr);

  /**
   * This method works as {@link #match(String)} but throws an exception if not matched.
   *
   * @param valueStr <i>optional</i> - the string to check for
   * @throws ArgumentException with code
   *           <dl>
   *           <dt>PARAM_MISSED</dt>
   *           <dd>if <code>valueStr</code> is <code>null</code></dd>
   *           <dt>PARAM_BAD</dt>
   *           <dd>if pattern is not matched</dd>
   *           </dl>
   */
  void matchEx(final String valueStr) throws ArgumentException;

  /**
   * Parses a value for this parameter from the given string. This method honors the parameters data
   * type and potentially other criteria defining a valid value (e.g. a pattern).
   *
   * @param valueStr <i>optional</i> - the string to parse the parameter value from
   * @return the parameter value according to the parameters type (see {@link #getType()}) or
   *         <code>null</code> in case <code>valueStr</code> was <code>null</code>.
   * @throws ArgumentException in case <code>valueStr</code> is not parsable as a value for this
   *           parameter.
   */
  Object parse(final String valueStr) throws ArgumentException;

  /**
   * Converts the given value to its external form as it is accepted by {@link #parse(String)}. For
   * most (ordinary) parameters this is simply a call to {@link String#valueOf(Object)}. In case the
   * parameter types {@link Object#toString()} method does not return the external form (e.g. for
   * enumerations), this method has to be implemented accordingly.
   *
   * @param value <i>mandatory</i> - the parameters value
   * @return the external form of the parameters value, never <code>null</code>
   * @throws InternalServiceException in case the given <code>value</code> does not match
   *           {@link #getType()}
   */
  String toString(final Object value) throws InternalServiceException;
}

实现ENUM基类。

public enum Parameters implements Parameter {
  /**
   * ANY ENUM VALUE
   */
  VALUE(new ParameterImpl<String>("VALUE", String.class, "[A-Za-z]{3,10}"));

  /**
   * The parameter wrapped by this enum constant.
   */
  private Parameter param;

  /**
   * Constructor.
   *
   * @param param <i>mandatory</i> - the value for {@link #param}
   */
  private Parameters(final Parameter param) {
    this.param = param;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public String getName() {
    return this.param.getName();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Class<?> getType() {
    return this.param.getType();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean match(final String valueStr) {
    return this.param.match(valueStr);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void matchEx(final String valueStr) {
    this.param.matchEx(valueStr);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Object parse(final String valueStr) throws ArgumentException {
    return this.param.parse(valueStr);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public String toString(final Object value) throws InternalServiceException {
    return this.param.toString(value);
  }
}

继承自基类的子类ENUM。

public enum ExtendedParameters implements Parameter {
  /**
   * ANY ENUM VALUE
   */
  VALUE(my.package.name.VALUE);

  /**
   * EXTENDED ENUM VALUE
   */
  EXTENDED_VALUE(new ParameterImpl<String>("EXTENDED_VALUE", String.class, "[0-9A-Za-z_.-]{1,20}"));

  /**
   * The parameter wrapped by this enum constant.
   */
  private Parameter param;

  /**
   * Constructor.
   *
   * @param param <i>mandatory</i> - the value for {@link #param}
   */
  private Parameters(final Parameter param) {
    this.param = param;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public String getName() {
    return this.param.getName();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Class<?> getType() {
    return this.param.getType();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean match(final String valueStr) {
    return this.param.match(valueStr);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void matchEx(final String valueStr) {
    this.param.matchEx(valueStr);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Object parse(final String valueStr) throws ArgumentException {
    return this.param.parse(valueStr);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public String toString(final Object value) throws InternalServiceException {
    return this.param.toString(value);
  }
}

最后使用泛型ParameterImpl添加一些实用程序。

public class ParameterImpl<T> implements Parameter {
  /**
   * The default pattern for numeric (integer, long) parameters.
   */
  private static final Pattern NUMBER_PATTERN = Pattern.compile("[0-9]+");

  /**
   * The default pattern for parameters of type boolean.
   */
  private static final Pattern BOOLEAN_PATTERN = Pattern.compile("0|1|true|false");

  /**
   * The name of the parameter, never <code>null</code>.
   */
  private final String name;

  /**
   * The data type of the parameter.
   */
  private final Class<T> type;

  /**
   * The validation pattern for the parameters values. This may be <code>null</code>.
   */
  private final Pattern validator;

  /**
   * Shortcut constructor without <code>validatorPattern</code>.
   *
   * @param name <i>mandatory</i> - the value for {@link #name}
   * @param type <i>mandatory</i> - the value for {@link #type}
   */
  public ParameterImpl(final String name, final Class<T> type) {
    this(name, type, null);
  }

  /**
   * Constructor.
   *
   * @param name <i>mandatory</i> - the value for {@link #name}
   * @param type <i>mandatory</i> - the value for {@link #type}
   * @param validatorPattern - <i>optional</i> - the pattern for {@link #validator}
   *          <dl>
   *          <dt style="margin-top:0.25cm;"><i>Note:</i>
   *          <dd>The default validation patterns {@link #NUMBER_PATTERN} or
   *          {@link #BOOLEAN_PATTERN} are applied accordingly.
   *          </dl>
   */
  public ParameterImpl(final String name, final Class<T> type, final String validatorPattern) {
    this.name = name;
    this.type = type;
    if (null != validatorPattern) {
      this.validator = Pattern.compile(validatorPattern);

    } else if (Integer.class == this.type || Long.class == this.type) {
      this.validator = NUMBER_PATTERN;
    } else if (Boolean.class == this.type) {
      this.validator = BOOLEAN_PATTERN;
    } else {
      this.validator = null;
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean match(final String valueStr) {
    if (null == valueStr) {
      return false;
    }
    if (null != this.validator) {
      final Matcher matcher = this.validator.matcher(valueStr);
      return matcher.matches();
    }
    return true;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void matchEx(final String valueStr) throws ArgumentException {
    if (false == this.match(valueStr)) {
      if (null == valueStr) {
        throw ArgumentException.createEx(ErrorCode.PARAM_MISSED, "The value must not be null",
            this.name);
      }
      throw ArgumentException.createEx(ErrorCode.PARAM_BAD, "The value must match the pattern: "
          + this.validator.pattern(), this.name);
    }
  }

  /**
   * Parse the parameters value from the given string value according to {@link #type}. Additional
   * the value is checked by {@link #matchEx(String)}.
   *
   * @param valueStr <i>optional</i> - the string value to parse the value from
   * @return the parsed value, may be <code>null</code>
   * @throws ArgumentException in case the parameter:
   *           <ul>
   *           <li>does not {@link #matchEx(String)} the {@link #validator}</li>
   *           <li>cannot be parsed according to {@link #type}</li>
   *           </ul>
   * @throws InternalServiceException in case the type {@link #type} cannot be handled. This is a
   *           programming error.
   */
  @Override
  public T parse(final String valueStr) throws ArgumentException, InternalServiceException {
    if (null == valueStr) {
      return null;
    }
    this.matchEx(valueStr);

    if (String.class == this.type) {
      return this.type.cast(valueStr);
    }
    if (Boolean.class == this.type) {
      return this.type.cast(Boolean.valueOf(("1".equals(valueStr)) || Boolean.valueOf(valueStr)));
    }
    try {
      if (Integer.class == this.type) {
        return this.type.cast(Integer.valueOf(valueStr));
      }
      if (Long.class == this.type) {
        return this.type.cast(Long.valueOf(valueStr));
      }
    } catch (final NumberFormatException e) {
      throw ArgumentException.createEx(ErrorCode.PARAM_BAD, "The value cannot be parsed as "
          + this.type.getSimpleName().toLowerCase() + ".", this.name);
    }

    return this.parseOther(valueStr);
  }

  /**
   * Field access for {@link #name}.
   *
   * @return the value of {@link #name}.
   */
  @Override
  public String getName() {
    return this.name;
  }

  /**
   * Field access for {@link #type}.
   *
   * @return the value of {@link #type}.
   */
  @Override
  public Class<T> getType() {
    return this.type;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final String toString(final Object value) throws InternalServiceException {
    if (false == this.type.isAssignableFrom(value.getClass())) {
      throw new InternalServiceException(ErrorCode.PANIC,
          "Parameter.toString(): Bad type of value. Expected {0} but is {1}.", this.type.getName(),
          value.getClass().getName());
    }
    if (String.class == this.type || Integer.class == this.type || Long.class == this.type) {
      return String.valueOf(value);
    }
    if (Boolean.class == this.type) {
      return Boolean.TRUE.equals(value) ? "1" : "0";
    }

    return this.toStringOther(value);
  }

  /**
   * Parse parameter values of other (non standard types). This method is called by
   * {@link #parse(String)} in case {@link #type} is none of the supported standard types (currently
   * String, Boolean, Integer and Long). It is intended for extensions.
   * <dl>
   * <dt style="margin-top:0.25cm;"><i>Note:</i>
   * <dd>This default implementation always throws an InternalServiceException.
   * </dl>
   *
   * @param valueStr <i>mandatory</i> - the string value to parse the value from
   * @return the parsed value, may be <code>null</code>
   * @throws ArgumentException in case the parameter cannot be parsed according to {@link #type}
   * @throws InternalServiceException in case the type {@link #type} cannot be handled. This is a
   *           programming error.
   */
  protected T parseOther(final String valueStr) throws ArgumentException, InternalServiceException {
    throw new InternalServiceException(ErrorCode.PANIC,
        "ParameterImpl.parseOther(): Unsupported parameter type: " + this.type.getName());
  }

  /**
   * Convert the values of other (non standard types) to their external form. This method is called
   * by {@link #toString(Object)} in case {@link #type} is none of the supported standard types
   * (currently String, Boolean, Integer and Long). It is intended for extensions.
   * <dl>
   * <dt style="margin-top:0.25cm;"><i>Note:</i>
   * <dd>This default implementation always throws an InternalServiceException.
   * </dl>
   *
   * @param value <i>mandatory</i> - the parameters value
   * @return the external form of the parameters value, never <code>null</code>
   * @throws InternalServiceException in case the given <code>value</code> does not match
   *           {@link #getClass()}
   */
  protected String toStringOther(final Object value) throws InternalServiceException {
    throw new InternalServiceException(ErrorCode.PANIC,
        "ParameterImpl.toStringOther(): Unsupported parameter type: " + this.type.getName());
  }
}

我自己也有同样的问题,我想把我的观点发表出来。我认为这样做有几个激励因素:

您希望有一些相关的枚举代码,但在不同的类中。在我的例子中,我有一个基类,在一个相关的枚举中定义了几个代码。在以后的某一天(今天!)我想为基类提供一些新功能,这也意味着枚举的新代码。 派生类既支持基类的枚举,也支持它自己的枚举。没有重复的enum值!如何为子类创建一个枚举,包括父类的枚举及其新值。

使用接口并不能真正解决问题:您可能会意外地获得重复的enum值。不可取的。

我最终只是组合了枚举:这确保了不会有任何重复的值,但代价是与相关类的绑定不那么紧密。但是,我认为重复的问题是我主要担心的……

我倾向于避免枚举,因为它们是不可扩展的。继续以OP为例,如果A在库中,而B在您自己的代码中,则如果A是枚举,则不能扩展A。这是我有时替换枚举的方法:

// access like enum: A.a
public class A {
    public static final A a = new A();
    public static final A b = new A();
    public static final A c = new A();
/*
 * In case you need to identify your constant
 * in different JVMs, you need an id. This is the case if
 * your object is transfered between
 * different JVM instances (eg. save/load, or network).
 * Also, switch statements don't work with
 * Objects, but work with int.
 */
    public static int maxId=0;
    public int id = maxId++;
    public int getId() { return id; }
}

public class B extends A {
/*
 * good: you can do like
 * A x = getYourEnumFromSomeWhere();
 * if(x instanceof B) ...;
 * to identify which enum x
 * is of.
 */
    public static final A d = new A();
}

public class C extends A {
/* Good: e.getId() != d.getId()
 * Bad: in different JVMs, C and B
 * might be initialized in different order,
 * resulting in different IDs.
 * Workaround: use a fixed int, or hash code.
 */
    public static final A e = new A();
    public int getId() { return -32489132; };
}

有一些陷阱要避免,请参阅代码中的注释。根据您的需要,这是枚举的可靠、可扩展的替代方案。