Java Generics. Почему он компилируется?
abstract class Type<K extends Number> {
abstract <K> void use1(Type<K> k); // Compiler error (Type parameter K is not within its bounds)
abstract <K> void use2(Type<? extends K> k); // fine
abstract <K> void use3(Type<? super K> k); // fine
}
Метод generic type K затеняет класс generic типа K, поэтому <K>
не соответствует <K extends Number>
в use1()
. Компилятор ничего не знает о новом родовом типе <K>
в use2()
и use3()
, но все же законно компилировать. Почему <? extends K>
(или <? super K>
) соответствует <K extends Number>
?
Ответы
Ответ 1
Прежде всего, перепишите его, чтобы избежать затенения:
abstract class Type<N extends Number> {
abstract <K> void use1(Type<K> k);
abstract <K> void use2(Type<? extends K> k);
abstract <K> void use3(Type<? super K> k);
}
В первом методе K
действует как параметр типа Type<N extends Number>
, поэтому его значение должно соответствовать значению Type
N
. Однако объявление метода не имеет ограничений на значение K
, поэтому оно не является законным. Было бы законным, если вы добавите необходимое ограничение на K
:
abstract <K extends Number> void use1(Type<K> k);
В следующих методах фактический параметр типа Type
неизвестен (?
), а K
налагает на него дополнительную привязку, поэтому в этих объявлениях нет ничего незаконного.
Вот более практичный пример с аналогичными объявлениями:
class MyList<N extends Number> extends ArrayList<N> {}
<K> void add1(MyList<K> a, K b) {
a.add(b); // Given the method declaration, this line is legal, but it
// violates type safety, since object of an arbitrary type K can be
// added to a list that expects Numbers
// Thus, declaration of this method is illegal
}
<K> void add2(MyList<? extends K> a, K b) {
// a.add(b) would be illegal inside this method, so that there is no way
// to violate type safety here, therefore declaration of this method is legal
}
<K> void add3(MyLisy<? super K> a, K b) {
a.add(b); // This line is legal, but it cannot violate type safey, since
// you cannot pass a list that doesn't expect K into this method
}
Ответ 2
Проблема заключается в том, что существует два типа K
. Это может быть яснее, если вы переименуете его.
abstract class Type<N extends Number> {
abstract <K extends Number> void use1(Type<K> k); // fine
abstract <K> void use2(Type<? extends K> k); // fine
abstract <K> void use3(Type<? super K> k); // fine
}
Есть случаи, когда вам необходимо предоставить дублирующую информацию, которую может сделать компилятор, и в других местах, где вы этого не сделали. В Java 7 она добавила нотацию <>
diamond, чтобы сообщить компилятору о выводе типов, которые ранее не были.
Чтобы проиллюстрировать, что я имею в виду. Вот несколько способов создания экземпляра универсального класса. Некоторые требуют, чтобы тип был указан дважды, другие - только один раз. Компилятор может вывести тип.
В общем, Java не выводит типы, если это возможно на большинстве других языков.
class Type<N extends Number> {
private final Class<N> nClass;
Type(Class<N> nClass) {
this.nClass = nClass;
}
static <N extends Number> Type<N> create(Class<N> nClass) {
return new Type<N>(nClass);
}
static void main(String... args) {
// N type is required.
Type<Integer> t1 = new Type<Integer>(Integer.class);
// N type inferred in Java 7.
Type<Integer> t2 = new Type<>(Integer.class);
// type is optional
Type<Integer> t3 = Type.<Integer>create(Integer.class);
// type is inferred
Type<Integer> t4 = create(Integer.class);
}
Ответ 3
Когда вы определяете метод следующим образом:
abstract <K> void use1(Type<K> k);
Вы эффективно скрываете тип K
в своем определении класса. Вы должны уметь определять такие методы:
abstract void use1(Type<K> k);
Ответ 4
Это серая область; javac 7 и 6 не согласны; JLS3 устарел; не знаю, где новая спецификация.