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

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>?

4b9b3361

Ответ 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 устарел; не знаю, где новая спецификация.