Существует ли разница между общими границами "Enum <T> и Foo" и "Enum <? Extends Foo>"

Являются ли эти две (допустимые) общие границы:

<T extends Enum<T> & MyInterface>
<T extends Enum<? extends MyInterface>>

то же самое?


Предположим, что у меня есть интерфейс

interface MyInterface {
    void someMethod();
}

И некоторые перечисления, которые его реализуют:

enum MyEnumA implements MyInterface {
    A, B, C;
    public void someMethod() {}
}

enum MyEnumB implements MyInterface {
    X, Y, Z;
    public void someMethod() {}
}

И я хочу потребовать, чтобы реализация использовала не только MyInterface, но также и то, что она является перечислением. "Стандартный" способ связан с пересечением:

static class MyIntersectionClass<T extends Enum<T> & MyInterface> {
    void use(T t) {}
}

Но я обнаружил, что это также работает:

static class MyWildcardClass<T extends Enum<? extends MyInterface>> {
    void use(T t) {}
}

С вышесказанным это компилируется:

public static void main(String[] args) throws Exception {
    MyIntersectionClass<MyEnumA> a = new MyIntersectionClass<MyEnumA>();
    a.use(MyEnumA.A);
    MyWildcardClass<MyEnumB> b = new MyWildcardClass<MyEnumB>();
    b.use(MyEnumB.X);
}

И эта работа работает как по назначению, так и по требованию выше для обоих случаев.

Есть ли разница между этими двумя границами, если да, и что одно лучше, чем другое?

Ответы

Ответ 1

В этом конкретном случае нет никакой разницы, поскольку параметр формального типа Enums является фактически типом self. Это потому, что на Enum нельзя наследовать так:

class MyEnumA extends Enum<MyEnum2> {}
class MyEnumB implements MyInterface {}

Итак, да, семантически они - одна и та же граница, но только потому, что это Enum.

Ответ 2

Как указывали другие, оба синтаксиса достигают тех же границ - и только из-за специального случая перечислений, где мы знаем, что T in Enum<T> должен быть непосредственно расширяющимся типом enum. Поэтому, ограничивая то, что может быть разрешено T, нет разницы.

Существует разница в возможном использовании экземпляров T, но это, вероятно, такой нюанс, что это не имеет значения. Учтите, что следующий оператор компилируется в MyIntersectionClass.use, но не MyWildcardClass.use:

T t2 = t.getDeclaringClass().newInstance();

Только они будут компилироваться в последнем:

MyInterface t2 = t.getDeclaringClass().newInstance();
Enum<? extends MyInterface> t3 = t.getDeclaringClass().newInstance();

Ответ 3

Поскольку второй опирается на тот факт, что перечисления Java реализованы как MyEnum extends Enum<MyEnum>, я предпочел бы первый, который не полагается на такие предположения и явно оговаривает ваши ограничения.

Ответ 4

Они будут делать то же самое, но я бы сказал, что T extends Enum<? extends MyInterface> является немного более стандартным и, следовательно, лучше, хотя бы потому, что он более часто и быстро узнаваем. Многие люди даже не знают о части дженериков &.

Вы также можете утверждать, что они читают несколько иначе. T extends Enum<T> & MyInterface Я бы читал как "перечисление, которое также является MyInterface". T extends Enum<? extends MyInterface> Я бы читал как "перечисление, которое реализует MyInterface". Таким образом, это вопрос личного предпочтения; Я предпочитаю последний.