是否可以在 Java 8 中扩展枚举?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/22083689/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 13:29:46  来源:igfitidea点击:

Is it possible to extend enum in Java 8?

javaenumsextendjava-8

提问by OldCurmudgeon

Just playing and came up with a sweet way to add functionality to enums in Java Enum toString() methodwith this.

只是玩玩,想出了一个甜蜜的方法来添加功能到enums 在Java Enum toString() 方法中使用this

Some further tinkering allowed me to nearlyalso add a tidy (i.e. not throwing an exception) reverse look-up but there's a problem. It's reporting:

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

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

Is there a way?

有办法吗?

The aim here is to silently add (via an interface implementation with a defaultmethod like I added politeNamein the linked answer) a lookupmethod that does the valueOffunction without throwing an exception. Is it possible? It is clearly now possible to extend enum- one of my major problems with Java until now.

这里的目的是静默添加(通过接口实现和defaultpoliteName在链接答案中添加的lookup方法)一个方法,该方法在不valueOf引发异常的情况下执行该功能。是否可以?很明显现在可以扩展enum-到目前为止,我在 Java 方面的主要问题之一。

Here's my failed attempt:

这是我失败的尝试:

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
}

采纳答案by Holger

You are over-complicating your design. If you are willing to accept that you can invoke a defaultmethod on an instance only, there entire code may look like this:

您的设计过于复杂。如果您愿意接受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
}

You can test it with:

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

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

An non-throwing/catching alternative would be:

非投掷/捕捉替代方案是:

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

to use like:

使用像:

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

回答by Damian Leszczyński - Vash

The case is the same as you can not create default toString()in interface. The enum already contains signature for static valueOf(String)method therefore you can not override it.

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

The enum are compile time constant and because of that it really doubtful that they will be extensible someday.

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

If you want to get the constant via name you can use this:

如果要通过名称获取常量,可以使用以下命令:

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

    }

回答by Radiodef

Here, there's basically two points. Specifically the reason it doesn't compile is 8.4.8.1:

在这里,基本上有两点。具体来说,它不编译的原因是8.4.8.1

It is a compile-time error if an instance method overrides a static method.

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

In other words, an enum can't implement HasValue because of the name clash.

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

Then there's the more general issue we have which is that static methods just cannot be 'overridden'. Since valueOfis a static method inserted by the compiler on the Enum-derived class itself, there's no way to change it. We also can't use interfaces to solve it since they do not have static methods.

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

In this specific case it's a place where composition can make this kind of thing lessrepetetive, for example:

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

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

(Plus, I'd recommend that over catching the exception anyway.)

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

I realize "you can't do it" is not really a desirable answer but I don't see a way around it due to the static aspect.

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

回答by OldCurmudgeon

I think I have an answer - it's hacky and uses reflection but seems to fit the brief - i.e. reverse lookup without methods in the enum and without throwing exception.

我想我有一个答案 - 它是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()));
    }
}

prints

印刷

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

Added

添加

Inspired by @Holger - this is what I feel is most like what I was looking for:

受到@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;
}