簡體   English   中英

如何使用泛型實現枚舉?

[英]How to implement enum with generics?

我有一個像這樣的通用接口:

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

對此有什么想法嗎?

你不能。 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() {};
}

不幸的是,它們都有缺點。

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

在這一點上,您獲得了成為真正恆定的enum值(以及隨之而來的所有特權)以及interface的獨特實現的好處,但是您擁有enum所需的全局可訪問性。

顯然,這增加了冗長,從而產生了復制/粘貼錯誤的可能性。 您可以將enum設為public並簡單地為它們的訪問添加一個額外的層。

傾向於使用這些特性的設計往往會受到脆弱的equals實現的影響,因為它們通常與其他一些獨特的值相結合,例如名稱,為了類似但不同的目的,它們可能會在不經意間在代碼庫中復制。 通過全面使用enum ,平等是一種免費贈品,不受這種脆弱行為的影響。

除了冗長之外,此類系統的主要缺點是在全局唯一鍵之間來回轉換的想法(例如,與 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;
}

這提供了與第一個選項相同的靈活性,並且它提供了一種通過反射獲取引用的機制,如果以后需要的話,從而避免以后需要實例化。 它還避免了第一個選項提供的許多容易出錯的復制/粘貼錯誤,因為如果第一個方法錯誤,它將無法編譯,而第二個方法不需要更改。 唯一需要注意的是,您應該確保以這種方式使用的enumpublic以避免任何人因為無法訪問內部enum而出現訪問錯誤; 如果您不想讓那些MetaDataKey穿過編組線路,則可以使用將它們從外部包中隱藏起來以自動丟棄它們(在編組期間,反射性地檢查enum是否可訪問,如果不可訪問,然后忽略鍵/值)。 如果維護了更明顯的static引用(因為enum實例就是這樣),除了提供兩種訪問實例的方法之外,通過public它沒有任何好處或損失。

我只是希望他們做到了,以便enum可以擴展 Java 中的對象。 也許在 Java 9 中?

最后一個選項並不能真正解決您的需求,因為您要求的是價值,但我懷疑這會達到實際目標。

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

通過使用這個 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();
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM