【问题标题】:Is it possible to extend enum in Java 8?是否可以在 Java 8 中扩展枚举?
【发布时间】:2014-03-31 18:40:22
【问题描述】:

刚开始玩,想出了一个甜蜜的方法来为Java Enum toString() method 和this 中的enums 添加功能。

一些进一步的修改让我几乎还添加了一个整洁的(即不抛出异常)反向查找,但有一个问题。它正在报告:

error: valueOf(String) in X cannot implement valueOf(String) in HasValue
public enum X implements PoliteEnum, ReverseLookup {
overriding method is static

有办法吗?

这里的目的是静默添加(通过带有default 方法的接口实现,就像我在链接答案中添加的politeName)一个lookup 方法,该方法执行valueOf 函数而不抛出异常。可能吗?显然现在可以扩展 enum - 迄今为止我使用 Java 的主要问题之一。

这是我失败的尝试:

public interface HasName {

    public String name();
}

public interface PoliteEnum extends HasName {

    default String politeName() {
        return name().replace("_", " ");
    }
}

public interface Lookup<P, Q> {

    public Q lookup(P p);
}

public interface HasValue {
    HasValue valueOf(String name);
}

public interface ReverseLookup extends HasValue, Lookup<String, HasValue> {

    @Override
    default HasValue lookup(String from) {
        try {
            return valueOf(from);
        } catch (IllegalArgumentException e) {
            return null;
        }
    }

}

public enum X implements PoliteEnum/* NOT ALLOWED :( , ReverseLookup*/ {

    A_For_Ism, B_For_Mutton, C_Forth_Highlanders;
}

public void test() {
    // Test the politeName
    for (X x : X.values()) {
        System.out.println(x.politeName());
    }
    // ToDo: Test lookup
}

【问题讨论】:

  • 我的直觉是“添加泛型”。我猜X.valueOf() 必须返回X,所以接口HasValue 应该是interface HasValue&lt;TThis&gt; 或类似的。请注意,这也意味着您不能进一步创建 X 的子类,但我又一次不确定这是否可能。
  • @millimoose - 这是我最初的想法 - 遗憾的是,问题是 valueOf 是 static 显然不能被覆盖。我怀疑需要一种完全不同的方法。
  • 您要在实例上调用lookup?
  • 为了它的价值,我只是将上述功能变成一种实用方法,而不是需要 3-4 个新接口的混乱。
  • @OldCurmudgeon 我和我相信这里的大多数人都关心完成工作,而不是突破界限的“优雅技巧”。至少对我来说,以最少的努力干净地完成工作比无论如何都将这项工作作为实例方法调用更有价值。了解 Java 是一门语言,其设计历来故意避开聪明、高阶编程等。如果您想做“魔术”,不同的平台可能会更好地满足您的需求。

标签: java enums extend java-8


【解决方案1】:

您的设计过于复杂。如果您愿意接受只能在实例上调用default 方法,那么整个代码可能如下所示:

interface ReverseLookupSupport<E extends Enum<E>> {
    Class<E> getDeclaringClass();
    default E lookup(String name) {
        try {
            return Enum.valueOf(getDeclaringClass(), name);
        } catch(IllegalArgumentException ex) { return null; }
    }
}
enum Test implements ReverseLookupSupport<Test> {
    FOO, BAR
}

您可以使用以下方法对其进行测试:

Test foo=Test.FOO;
Test bar=foo.lookup("BAR"), baz=foo.lookup("BAZ");
System.out.println(bar+"  "+baz);

非投掷/接球的替代方案是:

interface ReverseLookupSupport<E extends Enum<E>> {
    Class<E> getDeclaringClass();
    default Optional<E> lookup(String name) {
        return Stream.of(getDeclaringClass().getEnumConstants())
          .filter(e->e.name().equals(name)).findFirst();
}

像这样使用:

Test foo=Test.FOO;
Test bar=foo.lookup("BAR").orElse(null), baz=foo.lookup("BAZ").orElse(null);
System.out.println(bar+"  "+baz);

【讨论】:

  • 如果您不喜欢反射代码/临时数组,您可以将Stream.of(getDeclaringClass().getEnumConstants()) 替换为EnumSet.allOf(getDeclaringClass()).stream()。
  • 现在比我的整洁多了!!我喜欢您引入getDeclaringClass 并使其在一个界面中针对所有枚举的方式。巧妙地使用流 - 我可能会将它们流式传输到 Set 但这只是我。谢谢你的洞察力。我期待在 ernest 中使用 Java 8。
  • 您可以使用stream.collect(Collectors.toMap(Enum::name, Function.identity())) 将它们流式传输到Map&lt;String,Enum&gt;。但我认为对于典型的enum 大小,哈希查找并不比线性搜索快……
  • 不能在那里使用静态方法真的很烦人。使用任意值访问类方法是很奇怪的。
  • @flaschenpost:这实际上是一个缺乏真实用例的问题。如果您知道enum 的类别,则无论如何都可以简单地调用TypeOfEnum.valueOf(name)。这并不是说有任何东西阻止您将这些东西放入接受Class 参数的static 实用程序方法中。让它继承实例方法是问题的前提……
【解决方案2】:

这里,基本上有两点。特别是它不编译的原因是8.4.8.1:

如果实例方法覆盖静态方法,则会出现编译时错误。

换句话说,由于名称冲突,枚举无法实现 HasValue。

还有一个更普遍的问题是静态方法不能被“覆盖”。由于valueOf 是编译器在 Enum 派生类本身上插入的静态方法,因此无法更改它。我们也不能使用接口来解决它,因为它们没有静态方法。

在这种特定情况下,它是一个组合可以使这种事情减少重复的地方,例如:

public class ValueOfHelper<E extends Enum<E>> {
    private final Map<String, E> map = new HashMap<String, E>();

    public ValueOfHelper(Class<E> cls) {
        for(E e : EnumSet.allOf(cls))
            map.put(e.name(), e);
    }

    public E valueOfOrNull(String name) {
        return map.get(name);
    }
}

public enum Composed {
    A, B, C;

    private static final ValueOfHelper<Composed> HELPER = (
        new ValueOfHelper<Composed>(Composed.class)
    );

    public static Composed valueOfOrNull(String name) {
        return HELPER.valueOfOrNull(name);
    }
}

(另外,我建议无论如何都不要捕获异常。)

我意识到“你做不到”并不是一个真正理想的答案,但由于静态方面,我看不到解决方法。

【讨论】:

  • @Vash 你什么意思?我认为 OP 试图规避某些人不喜欢的 IllegalArgumentException。这确实与 valueOf 做同样的事情,它只是不抛出异常。此示例更旨在说明组合方面:可以通过这种方式添加其他静态实用程序。
  • 好吧...我想知道 OP 在做什么。但我认为你理解正确。
  • 只是为了确保您走在正确的轨道上,但是 java 8 中的接口 do 可以选择使用静态方法。不知道你是否意识到这一点。
  • @skiwi 他们这样做了,但似乎静态方法不是通过实现类“继承”的。或者至少我试过了,但它不起作用,所以它们似乎遵循与类上的静态方法不同的规则。
  • @Radiodef 据我所知,静态方法总是特定于类的。也就是说,通过扩展/实现,它们对其他类不可用。当然,您仍然可以通过ClassYouWantToCall.staticMethod() 或在某些情况下通过super.staticMethod() 给他们打电话。
【解决方案3】:

情况同在界面中不能创建默认toString()。枚举已经包含静态 valueOf(String) 方法的签名,因此您不能覆盖它。

枚举是编译时间常数,因此很怀疑它们有朝一日是否可扩展。

如果您想通过名称获取常量,可以使用:

public static <E extends Enum<E>> Optional<E> valueFor(Class<E> type, String name) {

       return Arrays.stream(type.getEnumConstants()).filter( x ->  x.name().equals(name)).findFirst();

    }

【讨论】:

  • 我相信你是对的,但我在关于这个问题的帖子中通过添加politeName 方法回避了这一点。我会对一些类似的开箱即用的想法感到满意。我仍然需要调用 enum values 或 valueOf 方法才能使查找工作。
  • 我不明白。在HasName 中,您有方法name,它是公共的而不是静态的,这就是它工作正常的原因。在HasValue 中,您声明valueOf(String) 女巫在枚举中是静态的。这会产生错误。我没有得到什么?
  • 我想你明白了 - 有没有可行的方法?也许我们可以通过反射调用values 并制作一个单例地图——我目前不知道。
  • 你不需要调用它。已经有一些东西可以获取常量
【解决方案4】:

我想我有一个答案 - 它很hacky并且使用反射,但似乎符合简短 - 即在枚举中没有方法并且没有抛出异常的反向查找。

public interface HasName {

    public String name();
}

public interface PoliteEnum extends HasName {

    default String politeName() {
        return name().replace("_", " ");
    }
}

public interface Lookup<P, Q> {

    public Q lookup(P p);
}

public interface ReverseLookup<T extends Enum<T>> extends Lookup<String, T> {

    @Override
    default T lookup(String s) {
        return (T) useMap(this, s);
    }

}

// Probably do somethiong better than this in the final version.
static final Map<String, Enum> theMap = new HashMap<>();

static Enum useMap(Object o, String s) {
    if (theMap.isEmpty()) {
        try {
            // Yukk!!
            Enum it = (Enum)o;
            Class c = it.getDeclaringClass();
            // Reflect to call the static method.
            Method method = c.getMethod("values");
            // Yukk!!
            Enum[] enums = (Enum[])method.invoke(null);
            // Walk the enums.
            for ( Enum e : enums) {
                theMap.put(e.name(), e);
            }
        } catch (Exception ex) {
            // Ewwww
        }
    }
    return theMap.get(s);
}

public enum X implements PoliteEnum, ReverseLookup<X> {

    A_For_Ism,
    B_For_Mutton,
    C_Forth_Highlanders;
}

public void test() {
    for (X x : X.values()) {
        System.out.println(x.politeName());
    }
    for (X x : X.values()) {
        System.out.println(x.lookup(x.name()));
    }
}

打印

A For Ism
B For Mutton
C Forth Highlanders
A_For_Ism
B_For_Mutton
C_Forth_Highlanders

添加

受@Holger 启发 - 这是我觉得最像我正在寻找的东西:

public interface ReverseLookup<E extends Enum<E>> extends Lookup<String, E> {

  // Map of all classes that have lookups.
  Map<Class, Map<String, Enum>> lookups = new ConcurrentHashMap<>();

  // What I need from the Enum.
  Class<E> getDeclaringClass();

  @Override
  default E lookup(String name) throws InterruptedException, ExecutionException {
    // What class.
    Class<E> c = getDeclaringClass();
    // Get the map.
    final Map<String, Enum> lookup = lookups.computeIfAbsent(c, 
              k -> Stream.of(c.getEnumConstants())
              // Roll each enum into the lookup.
              .collect(Collectors.toMap(Enum::name, Function.identity())));
    // Look it up.
    return c.cast(lookup.get(name));
  }

}

// Use the above interfaces to add to the enum.
public enum X implements PoliteName, ReverseLookup<X> {

    A_For_Ism,
    B_For_Mutton,
    C_Forth_Highlanders;
}

【讨论】:

  • 问题只是接口是实例而已。所以你必须做类似EnumClass.KnownConstant.lookup(unknownName);的事情。
  • @Radiodef - 是的 - 它的行为并不像它试图模拟的静态方法,但你必须承认这是一种改进。
  • 太复杂了。看看我的回答……
  • 您可以将(E) lookup.get(name) 更改为c.cast(lookup.get(name)) 以消除类型安全警告。
  • 关于多线程,这看起来是ConcurrentHashMap.computeIfAbsent(…) 的完美应用。这甚至可以简化代码和提供另一个“为什么 Java 8 如此酷”的例子……顺便说一句。请注意 collect 返回一个新地图,而不是填充您的 HashMap&lt;&gt;;您无需在此处手动创建HashMap。
猜你喜欢
  • 2011-02-08
  • 2021-11-23
  • 1970-01-01
  • 1970-01-01
  • 2011-09-24
  • 1970-01-01
  • 2010-12-10
  • 1970-01-01
相关资源
最近更新 更多