Подтвердить что ты не робот

Как реализовать перечисление с дженериками?

У меня есть общий интерфейс:

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

Есть ли идеи об этом?

4b9b3361

Ответ 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

Как разработчики 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;
}

Это обеспечивает такую ​​же гибкость, как и первый вариант, и предоставляет механизм для получения ссылки посредством отражения, если это становится необходимым позже, поэтому избегая необходимости в представлении позже. Он также избегает многих ошибок, связанных с ошибкой копирования/вставки, которые первый параметр предоставляет, поскольку он не будет компилироваться, если первый метод неверен, а второй метод не нужно изменять. Единственное замечание состоит в том, что вы должны убедиться, что enum, который должен использоваться таким образом, public, чтобы избежать доступа к ошибкам доступа, поскольку они не имеют доступа к внутреннему enum; если вы не хотите, чтобы эти MetaDataKey проходили через маршалированный провод, то их можно было скрывать из внешних пакетов, чтобы их автоматически отменить (во время маршалинга, рефлексивно проверить, доступен ли enum, и если это нет, затем игнорируйте ключ/значение). Нет ничего выигранного или потерянного, сделав его public, за исключением предоставления двух способов доступа к экземпляру, если поддерживаются более очевидные ссылки static (поскольку экземпляры enum просто так или иначе).

Я просто хочу, чтобы они сделали так, чтобы enum мог расширять объекты в Java. Возможно, в Java 9?

Окончательный вариант действительно не решает вашу потребность, поскольку вы просите значения, но я подозреваю, что это приближается к фактической цели.

Ответ 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;
    }
}