简体   繁体   English

如何使用泛型实现枚举?

[英]How to implement enum with generics?

I have a generic interface like this:我有一个像这样的通用接口:

interface A<T> {
    T getValue();
}

This interface has limited instances, hence it would be best to implement them as enum values.此接口的实例有限,因此最好将它们实现为枚举值。 The problem is those instances have different type of values, so I tried the following approach but it does not compile:问题是这些实例具有不同类型的值,因此我尝试了以下方法,但无法编译:

public enum B implements A {
    A1<String> {
        @Override
        public String getValue() {
            return "value";
        }
    },
    A2<Integer> {
        @Override
        public Integer getValue() {
            return 0;
        }
    };
}

Any idea about this?对此有什么想法吗?

You can't.你不能。 Java doesn't allow generic types on enum constants. Java 不允许对枚举常量使用泛型类型。 They are allowed on enum types, though:但是,它们被允许用于枚举类型:

public enum B implements A<String> {
  A1, A2;
}

What you could do in this case is either have an enum type for each generic type, or 'fake' having an enum by just making it a class:在这种情况下,您可以做的是为每个泛型类型设置一个枚举类型,或者通过将其设置为一个类来“伪造”一个枚举:

public class B<T> implements A<T> {
    public static final B<String> A1 = new B<String>();
    public static final B<Integer> A2 = new B<Integer>();
    private B() {};
}

Unfortunately, they both have drawbacks.不幸的是,它们都有缺点。

As Java developers designing certain APIs, we come across this issue frequently.在 Java 开发人员设计某些 API 时,我们经常遇到这个问题。 I was reconfirming my own doubts when I came across this post, but I have a verbose workaround to it:当我看到这篇文章时,我再次确认了我自己的疑虑,但我有一个详细的解决方法:

// class name is awful for this example, but it will make more sense if you
//  read further
public interface MetaDataKey<T extends Serializable> extends Serializable
{
    T getValue();
}

public final class TypeSafeKeys
{
    static enum StringKeys implements MetaDataKey<String>
    {
        A1("key1");

        private final String value;

        StringKeys(String value) { this.value = value; }

        @Override
        public String getValue() { return value; }
    }

    static enum IntegerKeys implements MetaDataKey<Integer>
    {
        A2(0);

        private final Integer value;

        IntegerKeys (Integer value) { this.value = value; }

        @Override
        public Integer getValue() { return value; }
    }

    public static final MetaDataKey<String> A1 = StringKeys.A1;
    public static final MetaDataKey<Integer> A2 = IntegerKeys.A2;
}

At that point, you gain the benefit of being a truly constant enum eration value (and all of the perks that go with that), as well being an unique implementation of the interface , but you have the global accessibility desired by the enum .在这一点上,您获得了成为真正恒定的enum值(以及随之而来的所有特权)以及interface的独特实现的好处,但是您拥有enum所需的全局可访问性。

Clearly, this adds verbosity, which creates the potential for copy/paste mistakes.显然,这增加了冗长,从而产生了复制/粘贴错误的可能性。 You could make the enum s public and simply add an extra layer to their access.您可以将enum设为public并简单地为它们的访问添加一个额外的层。

Designs that tend to use these features tend to suffer from brittle equals implementations because they are usually coupled with some other unique value, such as a name, which can be unwittingly duplicated across the codebase for a similar, yet different purpose.倾向于使用这些特性的设计往往会受到脆弱的equals实现的影响,因为它们通常与其他一些独特的值相结合,例如名称,为了类似但不同的目的,它们可能会在不经意间在代码库中复制。 By using enum s across the board, equality is a freebie that is immune to such brittle behavior.通过全面使用enum ,平等是一种免费赠品,不受这种脆弱行为的影响。

The major drawback to such as system, beyond verbosity, is the idea of converting back and forth between the globally unique keys (eg, marshaling to and from JSON).除了冗长之外,此类系统的主要缺点是在全局唯一键之间来回转换的想法(例如,与 JSON 之间的编组)。 If they're just keys, then they can be safely reinstantiated (duplicated) at the cost of wasting memory, but using what was previously a weakness-- equals --as an advantage.如果它们只是键,那么它们可以以浪费内存为代价安全地重新实例化(复制),但使用以前的弱点—— equals ——作为优势。

There is a workaround to this that provides global implementation uniqueness by cluttering it with an anonymous type per global instance:有一种解决方法可以通过将每个全局实例的匿名类型混杂在一起来提供全局实现的唯一性:

public abstract class BasicMetaDataKey<T extends Serializable>
     implements MetaDataKey<T>
{
    private final T value;

    public BasicMetaDataKey(T value)
    {
        this.value = value;
    }

    @Override
    public T getValue()
    {
        return value;
    }

    // @Override equals
    // @Override hashCode
}

public final class TypeSafeKeys
{
    public static final MetaDataKey<String> A1 =
        new BasicMetaDataKey<String>("value") {};
    public static final MetaDataKey<Integer> A2 =
        new BasicMetaDataKey<Integer>(0) {};
}

Note that each instance uses an anonymous implementation, but nothing else is needed to implement it, so the {} are empty.请注意,每个实例都使用匿名实现,但不需要其他任何东西来实现它,因此{}为空。 This is both confusing and annoying, but it works if instance references are preferable and clutter is kept to a minimum, although it may be a bit cryptic to less experienced Java developers, thereby making it harder to maintain.这既令人困惑又令人讨厌,但如果实例引用更可取并且混乱保持在最低限度,它就会起作用,尽管对于经验不足的 Java 开发人员来说可能有点神秘,从而使其更难维护。

Finally, the only way to provide global uniqueness and reassignment is to be a little more creative with what is happening.最后,提供全局唯一性和重新分配的唯一方法是对正在发生的事情更具创造性。 The most common use for globally shared interfaces that I have seen are for MetaData buckets that tend to mix a lot of different values, with different types (the T , on a per key basis):我见过的全局共享接口最常见的用途是用于 MetaData 存储桶,这些存储桶倾向于混合许多不同的值,具有不同的类型( T ,基于每个键):

public interface MetaDataKey<T extends Serializable> extends Serializable
{
    Class<T> getType();
    String getName();
}

public final class TypeSafeKeys
{
    public static enum StringKeys implements MetaDataKey<String>
    {
        A1;

        @Override
        public Class<String> getType() { return String.class; }

        @Override
        public String getName()
        {
            return getDeclaringClass().getName() + "." + name();
        }
    }

    public static enum IntegerKeys implements MetaDataKey<Integer>
    {
        A2;

        @Override
        public Class<Integer> getType() { return Integer.class; }

        @Override
        public String getName()
        {
            return getDeclaringClass().getName() + "." + name();
        }
    }

    public static final MetaDataKey<String> A1 = StringKeys.A1;
    public static final MetaDataKey<Integer> A2 = IntegerKeys.A2;
}

This provides the same flexibility as the first option, and it provides a mechanism for obtaining a reference via reflection, if it becomes necessary later, therefore avoiding the need for instantiable later.这提供了与第一个选项相同的灵活性,并且它提供了一种通过反射获取引用的机制,如果以后需要的话,从而避免以后需要实例化。 It also avoids a lot of the error prone copy/paste mistakes that the first option provides because it won't compile if the first method is wrong, and the second method does not need to change.它还避免了第一个选项提供的许多容易出错的复制/粘贴错误,因为如果第一个方法错误,它将无法编译,而第二个方法不需要更改。 The only note is that you should ensure that the enum s meant to be used in that fashion are public to avoid anyone getting access errors because they do not have access to the inner enum ;唯一需要注意的是,您应该确保以这种方式使用的enumpublic以避免任何人因为无法访问内部enum而出现访问错误; if you did not want to have those MetaDataKey s going across a marshaled wire, then keeping them hidden from outside packages could be used to automatically discard them (during marshaling, reflectively check to see if the enum is accessible, and if it is not, then ignore the key/value).如果您不想让那些MetaDataKey穿过编组线路,则可以使用将它们从外部包中隐藏起来以自动丢弃它们(在编组期间,反射性地检查enum是否可访问,如果不可访问,然后忽略键/值)。 There is nothing gained or lost by making it public except providing two ways to access the instance, if the more obvious static references are maintained (as the enum instances are just that anyway).如果维护了更明显的static引用(因为enum实例就是这样),除了提供两种访问实例的方法之外,通过public它没有任何好处或损失。

I just wish that they made it so that enum s could extend objects in Java.我只是希望他们做到了,以便enum可以扩展 Java 中的对象。 Maybe in Java 9?也许在 Java 9 中?

The final option does not really solve your need, as you were asking for values, but I suspect that this gets toward the actual goal.最后一个选项并不能真正解决您的需求,因为您要求的是价值,但我怀疑这会达到实际目标。

If JEP 301: Enhanced Enums gets accepted, then you will be able to use syntax like this (taken from proposal):如果JEP 301: Enhanced Enums被接受,那么你将能够使用这样的语法(取自提案):

enum Primitive<X> {
    INT<Integer>(Integer.class, 0) {
        int mod(int x, int y) { return x % y; }
        int add(int x, int y) { return x + y; }
    },
    FLOAT<Float>(Float.class, 0f)  {
        long add(long x, long y) { return x + y; }
    }, ... ;

    final Class<X> boxClass;
    final X defaultValue;

    Primitive(Class<X> boxClass, X defaultValue) {
        this.boxClass = boxClass;
        this.defaultValue = defaultValue;
    }
}

By using this Java annotation processor https://github.com/cmoine/generic-enums , you can write this:通过使用这个 Java 注释处理器https://github.com/cmoine/generic-enums ,你可以这样写:

import org.cmoine.genericEnums.GenericEnum;
import org.cmoine.genericEnums.GenericEnumParam;

@GenericEnum
public enum B implements A<@GenericEnumParam Object> {
    A1(String.class, "value"), A2(int.class, 0);

    @GenericEnumParam
    private final Object value;

    B(Class<?> clazz, @GenericEnumParam Object value) {
        this.value = value;
    }

    @GenericEnumParam
    @Override
    public Object getValue() {
        return value;
    }
}

The annotation processor will generate an enum BExt with hopefully all what you need!注释处理器将生成一个枚举BExt其中包含您需要的所有内容!

if you prefer you can also use this syntax:如果您愿意,也可以使用以下语法:

import org.cmoine.genericEnums.GenericEnum;
import org.cmoine.genericEnums.GenericEnumParam;

@GenericEnum
public enum B implements A<@GenericEnumParam Object> {
    A1(String.class) {
        @Override
        public @GenericEnumParam Object getValue() {
            return "value";
        }
    }, A2(int.class) {
        @Override
        public @GenericEnumParam Object getValue() {
            return 0;
        }
    };

    B(Class<?> clazz) {
    }

    @Override
    public abstract @GenericEnumParam Object getValue();
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM