Подстановочный знак Java странное поведение, когда класс является общим - PullRequest
6 голосов
/ 17 мая 2011

Я подумал, что у меня есть хорошее понимание дженериков Java.

Этот код НЕ СОСТАВЛЯЕТ и я знаю почему.

Мы можем перейти только к методу тестирования. Список типа Animal или его супертипа (например, List of Objects)

package scjp.examples.generics.wildcards;

import java.util.ArrayList;
import java.util.List;

class Animal {}
class Mammal extends Animal {}
class Dog extends Mammal {}

public class Test {

    public void test(List<? super Animal> col) {
        col.add(new Animal());
        col.add(new Mammal());
        col.add(new Dog());
    }

    public static void main(String[] args) {
        List<Animal> animalList = new ArrayList<Animal>();
        List<Mammal> mammalList = new ArrayList<Mammal>();
        List<Dog> dogList = new ArrayList<Dog>();

        new Test().test(animalList);
        new Test().test(mammalList); // Error: The method test(List<? super Animal>) in the type Test is not applicable for the arguments (List<Mammal>)  
        new Test().test(dogList);    // Error: The method test(List<? super Animal>) in the type Test is not applicable for the arguments (List<Dog>)

        Dog dog = dogList.get(0);
    }        
}

Но тут возникает странная часть (по крайней мере, для меня).

Если мы объявим класс Test как универсальный, добавив только , то он COMPILES !и выдает java.lang.ClassCastException:

public class Test<T> {
...
}

,

Exception in thread "main" java.lang.ClassCastException: scjp.examples.generics.wildcards.Animal cannot be cast to scjp.examples.generics.wildcards.Dog

Мой вопрос заключается в том, почему добавление универсального типа класса (который нигде не используется) вызвало компиляцию класса иизменилось поведение подстановочных знаков?

Ответы [ 4 ]

7 голосов
/ 17 мая 2011

Выражение new Test() имеет сырой тип.Спецификация языка Java определяет типы членов необработанных типов следующим образом:

Тип конструктора (§8.8), метод экземпляра (§8.8, §9.4),или нестатическое поле (§8.3) M необработанного типа C, который не унаследован от его суперклассов или суперинтерфейсов, - это стирание его типа в обобщенном объявлении, соответствующем C. Тип статического члена необработанного типа C:такой же, как его тип в обобщенном объявлении, соответствующем C.

Стирание List<? super Animal> равно List.

Смысл этого определения, вероятно, заключается в том, что необработанные типыпредназначен для использования в качестве универсальных типов из неуниверсального унаследованного кода, где параметры типов отсутствуют.Они не были разработаны и менее чем оптимальны, оставляя параметр типа неопределенным;для этого нужны подстановочные типы, т. е. если вы кодируете уровень соответствия компилятора, превышающий 1,5, вы должны написать

    Test<?> test = makeTest();
    test.test(animalList);
    test.test(mammalList);
    test.test(dogList);

и радоваться (или проклинать, в зависимости от обстоятельств), снова увидев ошибки компиляции.

1 голос
/ 17 мая 2011

Вы параметризовали тип, добавив:

public class Test<T> {

Но затем вы используете его в качестве необработанного типа, выполнив:

new Test()

Итак, все ставки сняты. Чтобы обеспечить совместимость с устаревшим кодом, компилятор пропускает его, но теперь это не проверка типов. Это сгенерирует предупреждение компилятора.

1 голос
/ 17 мая 2011

Интересный вопрос.

Я подтвердил ваш результат, скомпилировав его для себя, и он действительно скомпилируется (с предупреждениями), если вы добавите параметр неиспользуемого типа. Тем не менее, он не может быть скомпилирован снова, если вы на самом деле указываете тип для параметра типа:

    new Test<Object>().test(animalList);
    new Test<Object>().test(mammalList);
    new Test<Object>().test(dogList);

Я подозреваю, что, поскольку вы используете непроверенную операцию для создания объекта Test, компилятор не пытается проверять другие типы параметров и рассматривает все это как непроверенное / небезопасное. Когда вы указываете тип, он возвращается к своему предыдущему поведению.

0 голосов
/ 17 мая 2011

Если вы добавите <T> к вашему классу Test, а затем заполните родовое значение чем-то, что ошибка компиляции вернетУ меня недостаточно информации, чтобы проверить что-либо за пределами этого уровня.

...