如何用泛型实现枚举?

Posted

技术标签:

【中文标题】如何用泛型实现枚举?【英文标题】:How to implement enum with generics? 【发布时间】:2012-07-15 08:21:14 【问题描述】:

我有一个这样的通用接口:

interface A<T> 
    T getValue();

此接口的实例有限,因此最好将它们实现为枚举值。问题是这些实例具有不同类型的值,所以我尝试了以下方法但它无法编译:

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

对此有什么想法吗?

【问题讨论】:

【参考方案1】:

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

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

在这种情况下,您可以为每个泛型类型设置一个枚举类型,或者通过将其设为类来“假”拥有一个枚举:

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

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

【讨论】:

这种方法不能解决我的问题,除非我使用enum B implemts A&lt;Object&gt;,这反过来又使通用接口变得毫无意义。 这就是我想要说明的重点——你的问题无法通过使用单个 Enum 来解决。 现在至少我明白enum 不太适合我的要求。谢谢! @Pokechu22 对我来说听起来不错,尤其是与允许原语作为类型参数的新提议结合使用时。【参考方案2】:

作为设计某些 API 的 Java 开发人员,我们经常遇到这个问题。当我看到这篇文章时,我再次确认了自己的怀疑,但我有一个详细的解决方法:

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

此时,您将受益于成为真正恒定的 enumeration 值(以及随之而来的所有好处),以及 interface 的独特实现,但您拥有全局enum 所需的可访问性。

显然,这会增加冗长,从而可能导致复制/粘贴错误。您可以将enums 设为public 并简单地为他们的访问添加一个额外的层。

倾向于使用这些功能的设计往往会受到脆弱的equals 实现的影响,因为它们通常与其他一些独特的值(例如名称)结合在一起,这些值可能会在代码库中出于相似但不同的目的而不知不觉地重复.通过全面使用enums,平等是一种免费赠品,不受这种脆弱行为的影响。

除了冗长之外,此类系统的主要缺点是在全局唯一键之间来回转换的想法(例如,与 JSON 之间的编组)。如果它们只是键,那么可以以浪费内存为代价安全地重新实例化(复制)它们,但使用以前的弱点--equals--作为优势。

对此有一种解决方法,它通过在每个全局实例中使用匿名类型来提供全局实现的唯一性:

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

请注意,每个实例都使用匿名实现,但不需要其他任何东西来实现它,因此 为空。这既令人困惑又烦人,但如果实例引用更可取并且将混乱保持在最低限度,它就可以工作,尽管对于经验不足的 Java 开发人员来说可能有点神秘,从而使其更难维护。

最后,提供全局唯一性和重新分配的唯一方法是对正在发生的事情更有创意。我见过的全局共享接口最常见的用途是元数据存储桶,它倾向于混合许多不同的值和不同的类型(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;

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

我只是希望他们能够做到这一点,以便enums 可以在 Java 中扩展对象。也许在 Java 9 中?

最后一个选项并不能真正解决您的需求,因为您要求的是价值,但我怀疑这会达到实际目标。

【讨论】:

非常感谢分享这个解决方法!其实你不需要使用enum,只需使用匿名内部类,这就是我现在正在做的。 没问题。匿名内部类是中间的例子。为了可维护性,我倾向于避免使用匿名类(编译后的代码都会与 $1$2 匿名类型混淆,许多开发人员会错过成为匿名类型的原因),但这绝对是一种有效且较小的方法。跨度> 我非常感谢这个答案。感谢您的努力。 这是一篇旧文章,但您能总结一下您提供的实现的成本和收益吗?我很难理解为什么我会选择其中一个而不是其他。【参考方案3】:

如果 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;
    

【讨论】:

注意其实已经撤回了:openjdk.java.net/jeps/301 @TiStrga 你在哪里看到的? latest message 是从 9 月开始的,称它已死 @TiStrga 于 2020-09-29 他们写了we are now withdrawing this JEP,您所指的 12 月的电子邮件来自 2018 年 12 月。这个 JEP 显然已经死了。 我看错了!我已经删除了误导性链接;谢谢。可惜,这个语言特性会清理大量代码。【参考方案4】:

通过使用这个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;
    

注解处理器将生成一个枚举BExt,希望能满足您的所有需求!

如果您愿意,也可以使用以下语法:

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

【讨论】:

以上是关于如何用泛型实现枚举?的主要内容,如果未能解决你的问题,请参考以下文章

如何用Jpype创建HashMap和ArrayList

如何要求泛型参数是实现接口的枚举?

如何使用泛型作为枚举的键

枚举作为泛型类型并用于某些注释

如何用R语言for循环形成112358

泛型与枚举