如何使用泛型实现枚举?

2022-08-31 13:39:55

我有一个像这样的通用界面:

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

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


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

在这一点上,您将获得成为真正恒定的eration值(以及随之而来的所有特权)的好处,并且是 的唯一实现,但是您拥有.enuminterfaceenum

显然,这增加了冗长性,从而产生了复制/粘贴错误的可能性。您可以制作s,然后简单地为其访问添加额外的层。enumpublic

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

除了冗长之外,诸如系统的主要缺点是在全局唯一键之间来回转换的想法(例如,与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开发人员来说可能有点神秘,从而使维护变得更加困难。{}

最后,提供全局唯一性和重新分配的唯一方法是对正在发生的事情更具创造性。我所见过的全局共享接口最常见的用法是 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;
}

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

我只是希望他们能做到,这样s就可以扩展Java中的对象。也许在Java 9中?enum

最后一个选项并不能真正解决你的需求,因为你要求价值观,但我怀疑这已经朝着实际目标迈进了。