如何用泛型实现枚举?

时间:2021-04-24 11:34:35

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?

有什么想法吗?

3 个解决方案

#1


45  

You can't. Java doesn't allow generic types on enum constants. They are allowed on enum types, though:

你不能。 Java不允许在枚举常量上使用泛型类型。但它们在枚举类型上是允许的:

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.

不幸的是,它们都有缺点。

#2


27  

As Java developers designing certain APIs, we come across this issue frequently. I was reconfirming my own doubts when I came across this post, but I have a verbose workaround to it:

当Java开发人员设计某些API时,我们经常遇到这个问题。当我遇到这篇文章时,我正在重新确认自己的疑虑,但我有一个冗长的解决方法:

// 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 enumeration 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.

此时,您将获得成为真正的常量枚举值(以及与之相关的所有特权)的好处,以及该接口的唯一实现,但您具有枚举所需的全局可访问性。

Clearly, this adds verbosity, which creates the potential for copy/paste mistakes. You could make the enums public and simply add an extra layer to their access.

显然,这会增加冗长,从而产生复制/粘贴错误的可能性。您可以将枚举公开,只需在其访问中添加一个额外的图层即可。

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. By using enums across the board, equality is a freebie that is immune to such brittle behavior.

倾向于使用这些特征的设计往往会受到脆弱的等同实现的影响,因为它们通常与其他一些独特的值相结合,例如名称,这可能会在代码库中无意中重复,以实现类似但不同的目的。通过全面使用枚举,平等是一种不受这种脆弱行为影响的免费赠品。

The major drawback to such as system, beyond verbosity, is the idea of converting back and forth between the globally unique keys (e.g., marshaling to and from 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.

除了详细程度之外,诸如系统之类的主要缺点是在全局唯一密钥之间来回转换的想法(例如,编组到JSON和从JSON编组)。如果它们只是键,那么它们可以以浪费内存为代价安全地重新实例化(重复),但使用以前的弱点 - 等于 - 作为优势。

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 enums 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; if you did not want to have those MetaDataKeys 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). 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).

这提供了与第一个选项相同的灵活性,并且它提供了一种通过反射获得引用的机制,如果以后需要它,则因此避免以后需要实例化。它还避免了第一个选项提供的许多容易出错的复制/粘贴错误,因为如果第一个方法错误则不会编译,第二个方法不需要更改。唯一需要注意的是,您应该确保以这种方式使用的枚举是公开的,以避免任何人因为无法访问内部枚举而获得访问错误;如果你不想让那些MetaDataKeys穿过编组线,那么将它们从外部包中隐藏起来可以用来自动丢弃它们(在编组期间,反射性地检查枚举是否可访问,如果不可访问,那么忽略键/值)。除了提供两种访问实例的方法之外,如果维护更明显的静态引用(因为enum实例无论如何都是这样),除了提供两种访问实例的方法之外,没有任何获得或丢失的东西。

I just wish that they made it so that enums could extend objects in Java. Maybe in Java 9?

我只是希望他们这样做,以便枚举可以扩展Java中的对象。也许在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.

最终选项并没有真正解决您的需求,因为您要求的是价值观,但我怀疑这是实现目标的。

#3


0  

If JEP 301: Enhanced Enums gets accepted, then you will be able to use syntax like this (take from proposal):

如果JEP 301:增强的枚举被接受,那么您将能够使用这样的语法(从提案中获取):

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

#1


45  

You can't. Java doesn't allow generic types on enum constants. They are allowed on enum types, though:

你不能。 Java不允许在枚举常量上使用泛型类型。但它们在枚举类型上是允许的:

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.

不幸的是,它们都有缺点。

#2


27  

As Java developers designing certain APIs, we come across this issue frequently. I was reconfirming my own doubts when I came across this post, but I have a verbose workaround to it:

当Java开发人员设计某些API时,我们经常遇到这个问题。当我遇到这篇文章时,我正在重新确认自己的疑虑,但我有一个冗长的解决方法:

// 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 enumeration 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.

此时,您将获得成为真正的常量枚举值(以及与之相关的所有特权)的好处,以及该接口的唯一实现,但您具有枚举所需的全局可访问性。

Clearly, this adds verbosity, which creates the potential for copy/paste mistakes. You could make the enums public and simply add an extra layer to their access.

显然,这会增加冗长,从而产生复制/粘贴错误的可能性。您可以将枚举公开,只需在其访问中添加一个额外的图层即可。

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. By using enums across the board, equality is a freebie that is immune to such brittle behavior.

倾向于使用这些特征的设计往往会受到脆弱的等同实现的影响,因为它们通常与其他一些独特的值相结合,例如名称,这可能会在代码库中无意中重复,以实现类似但不同的目的。通过全面使用枚举,平等是一种不受这种脆弱行为影响的免费赠品。

The major drawback to such as system, beyond verbosity, is the idea of converting back and forth between the globally unique keys (e.g., marshaling to and from 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.

除了详细程度之外,诸如系统之类的主要缺点是在全局唯一密钥之间来回转换的想法(例如,编组到JSON和从JSON编组)。如果它们只是键,那么它们可以以浪费内存为代价安全地重新实例化(重复),但使用以前的弱点 - 等于 - 作为优势。

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 enums 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; if you did not want to have those MetaDataKeys 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). 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).

这提供了与第一个选项相同的灵活性,并且它提供了一种通过反射获得引用的机制,如果以后需要它,则因此避免以后需要实例化。它还避免了第一个选项提供的许多容易出错的复制/粘贴错误,因为如果第一个方法错误则不会编译,第二个方法不需要更改。唯一需要注意的是,您应该确保以这种方式使用的枚举是公开的,以避免任何人因为无法访问内部枚举而获得访问错误;如果你不想让那些MetaDataKeys穿过编组线,那么将它们从外部包中隐藏起来可以用来自动丢弃它们(在编组期间,反射性地检查枚举是否可访问,如果不可访问,那么忽略键/值)。除了提供两种访问实例的方法之外,如果维护更明显的静态引用(因为enum实例无论如何都是这样),除了提供两种访问实例的方法之外,没有任何获得或丢失的东西。

I just wish that they made it so that enums could extend objects in Java. Maybe in Java 9?

我只是希望他们这样做,以便枚举可以扩展Java中的对象。也许在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.

最终选项并没有真正解决您的需求,因为您要求的是价值观,但我怀疑这是实现目标的。

#3


0  

If JEP 301: Enhanced Enums gets accepted, then you will be able to use syntax like this (take from proposal):

如果JEP 301:增强的枚举被接受,那么您将能够使用这样的语法(从提案中获取):

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