Коллекции Java: список <Animal>тигр = новый ArrayList <Tiger>() НЕПРАВИЛЬНО - PullRequest
1 голос
/ 28 марта 2012

Класс тигра происходит от класса животных.

Когда я заявляю: List<Animal> tiger = new ArrayList<Tiger>();Я буду ошибка во время компиляции.

Но я думаю, что эта линия верна для полиморфизма.Кто может объяснить мне, пожалуйста.

Ответы [ 5 ]

5 голосов
/ 28 марта 2012

вы не можете сделать

List<Animal> tiger = new ArrayList<Tiger>();

что в яве. Общий тип слева должен быть точно равен (или не обязательно должен быть равен, если в игре используются подстановочные знаки - ? extends T или ? super T) универсальному типу справа.

Если бы это было возможно, то было бы невозможно добавить новый Lion в список, объявленный как список Animal s - это не имело бы смысла.

Что вы можете сделать:

List<Animal> tigers = new ArrayList<Animal>();
tigers.add(new Tiger());

(вся семья Animal с, включая Tiger с)

или

List<? extends Animal> tigers = new ArrayList<Tiger>();
tigers.add(new Tiger()); // Adding is immpossible now - list can be read only now! 

(только подклассы Animal) - список можно прочитать только сейчас!

4 голосов
/ 28 марта 2012

A List<Animal> позволит вам добавить симпатичного маленького щенка. Что бы съели тигры в ArrayList<Tiger>.

Полиморфно говоря, вы бы получили

List<Tiger> tigers = new ArrayList<Tiger>();

Что позволит вам заменить использование любой реализации List<Tiger>, если вы того пожелаете, полагаясь и используя функциональность, определенную интерфейсом. То, что вы пытаетесь сделать, - это не полиморфизм, это просто небезопасное обращение (особенно для вышеупомянутого щенка), и оно не будет работать по причинам, показанным выше.

2 голосов
/ 28 марта 2012

Причины этого основаны на том, как Java реализует дженерики.Лучший способ объяснить это - сначала использовать массивы.

Пример массива

С массивами вы можете сделать это:

Integer[] myInts = {1,2,3,4};
Number[] myNumber = myInts;

Но что произойдет, если вы попытаетесь это сделать?

Number[0] = 3.14; //attempt of heap pollution

Эта последняя строка скомпилируется просто отлично, но если вы запустите этот код, вы можете получить ArrayStoreException.

Это означает, что вы можете обмануть компилятор, но вы не можете обмануть систему типов времени выполнения.И это так, потому что массивы - это то, что мы называем типами reifiable .Это означает, что во время выполнения Java знает, что этот массив был фактически создан как массив целых чисел, к которым просто случается обращение через ссылку типа Number[].

Итак, как вы можете видеть, одна вещь - этоРеальный тип объекта, другая вещь - это тип ссылки, которую вы используете для доступа к ней, верно?

Проблема с обобщением Java

ТеперьПроблема с универсальными типами Java заключается в том, что информация о типах отбрасывается компилятором и недоступна во время выполнения.Этот процесс называется тип стирания .Есть веская причина для реализации таких обобщений в Java, но это длинная история, и она связана с бинарной совместимостью с уже существующим кодом.

Но важным моментом здесь является то, что, поскольку во время выполнениянет информации о типе, нет способа гарантировать, что мы не совершаем загрязнение кучи.

Например,

List<Integer> myInts = new ArrayList<Integer>();
myInts.add(1);
myInts.add(2);

List<Number> myNums = myInts;
myNums.add(3.14); //heap polution

Если компилятор Java не мешает вам делать это при компиляцииВ то же время система типов времени выполнения не может вас остановить, потому что во время выполнения невозможно определить, что этот список должен быть только списком целых чисел.Среда выполнения Java позволила бы вам поместить в этот список все, что вы хотите, когда он должен содержать только целые числа, потому что, когда он был создан, он был объявлен как список целых чисел.

Таким образом, разработчики Java сделалиуверен, что вы не можете обмануть компилятор.Если вы не можете обмануть компилятор (как мы можем это сделать с массивами), вы также не можете обмануть систему типов времени выполнения.

Таким образом, мы говорим, что универсальные типы non-reifiable .

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

Ковариация

С помощью ковариации вы можете читать элементы из структуры, но выне могу ничего написать в него.Все это действительные декларации.

List<? extends Number> myNums = new ArrayList<Integer>();
List<? extends Number> myNums = new ArrayList<Float>()
List<? extends Number> myNums = new ArrayList<Double>()

И вы можете прочитать из myNums:

Number n = myNums.get(0);

Поскольку вы можете быть уверены, что, что бы ни содержал фактический список, его можно преобразовать вчисло (ведь все, что расширяет число, является числом, верно?)

Однако вам не разрешено помещать что-либо в ковариантную структуру.

myNumst.add(45L);

Это не разрешенопотому что Java не может гарантировать, что является реальным типом реального объекта.Это может быть что угодно, что расширяет Number, но компилятор не может быть уверен.Таким образом, вы можете читать, но не писать.

Контравариантность

С помощью контравариантности вы можете делать противоположное.Вы можете поместить вещи в общую структуру, но вы не можете читать из нее.

List<Object> myObjs = new List<Object();
myObjs.add("Luke");
myObjs.add("Obi-wan");

List<? super Number> myNums = myObjs;
myNums.add(10);
myNums.add(3.14);

В этом случае фактическая природа объекта - это Список объектов, и с помощью контравариантности вы можете поместить числав это, в основном, потому что числа имеют Объект в качестве общего предка.Таким образом, все числа являются объектами, и поэтому это действительно.

Однако вы не можете безопасно читать что-либо из этой контравариантной структуры, предполагая, что вы получите число.

Number myNum = myNums.get(0); //compiler-error

Как видите, если бы компилятор позволил вам написать эту строку, вы получите ClassCastException во время выполнения.

Принцип Get / Put

Таким образом, используйте ковариацию, когда вы намерены извлекать универсальные значения только из структуры, используйте контравариантность, когда вы только намереваетесь поместить универсальные значения в структуру, и используйте точный универсальный тип, когда вы собираетесь сделать оба.

Лучший пример, который у меня есть, - это копирование любых чисел из одного списка в другой.

public static void copy(List<? extends Number> source, List<? super Number> destiny) {
    for(Number number : source) {
        destiny.add(number);
    }
}

Благодаря силам ковариации и контравариантности это работает для случая, подобного этому:

List<Integer> myInts = asList(1,2,3,4);
List<Integer> myDoubles = asList(3.14, 6.28);
List<Object> myObjs = new ArrayList<Object>();

copy(myInts, myObjs);
copy(myDoubles, myObjs);
1 голос
/ 28 марта 2012

О.Да, ты правда.Ваш код верен в мышлении полиморфизма.Но посмотрите на мой код, который я использую в течение долгого времени, когда я просто новичок.И вы поймете, почему вы должны благодарить Collection:

class Animal{   
}
class Tiger extends Animal{

}
public class Test {

    public static void main (String[] args){

        List<Animal> animal = new ArrayList<Animal>();  //obvious
        List<Tiger> tiger = new ArrayList<Tiger>();     //obvious

        List<Animal> tigerList = new ArrayList<Tiger>();  //error at COMPILE-TIME
        Animal[] tigerArray = new Tiger[2];     //like above but no error but....

        Animal tmpAnimal = new Animal();
        /*
         * will meet RUN-TIME error at below line when use Array
         * but Collections can prevent this before at COMPILE-TIME
         */
        tigerArray[0] = tmpAnimal;   //Oh NOOOO. RUN-TIME EXCEPTION

        /*
         * Below examples WRONG for both Collection and Array
         * Because here is Polymorphism problem. I just want to make more clearer
         */
        List<Tiger> animalList = new ArrayList<Animal>();
        Tiger[] animalArray = new Animal[2];        
    }

}

. Как вы видите мой код выше, Collections настолько "интеллектуален", когда мешает вам использовать List<Animal> tigerList = new ArrayList<Tiger>();

Вы должны представить, если кто-то использует: tigerList.add(a Lion, a Cat,......); ---> ОШИБКА.

Итак, чтобы подвести итог, вот другое:

ARRAY: проверка в RUN-TIME.Вы будете чувствовать себя более комфортно, но ОПАСНЫЕ

КОЛЛЕКЦИИ: проверьте в COMPILE-TIME.Вы будете злиться, потому что заметили ошибку.Но вы предотвратите ошибки при запуске.!!!!

Возможно ниже пост закончен по вашему вопросу.Но я предлагаю вам использовать WildCard, например:

List<? extends Animal> tigerList = new ArrayList<Tiger>();

Да.Вы можете увидеть идею за этой линией.Но самое интересное: это помешает вам изменить список.в этом случае add метод.Например:

tigerList.add(TIGER); ERROR

да.Это также помешает вам добавить тигра :)

1 голос
/ 28 марта 2012

Я согласен, это сбивает с толку.Вот что может пойти не так, если разрешить этот тип заявления:

List<Tiger> tigers = new ArrayList<Tiger>();  // This is allowed.
List<Animal> animals = tigers;  // This isn't allowed.
tigers.add(new Lion());  // This puts a Lion in tigers!
...