Путаница полиморфизма Java - PullRequest
8 голосов
/ 13 мая 2009

Вопрос, приведенный ниже, взят из книги Java SCJP5 Кэти Сьерра и Берт Бейтс. Данный метод объявлен как:

public static <E extends Number> List<E> process(List<E> nums)

Программист хочет использовать такой метод:

// INSERT DECLARATIONS HERE
output = process(input);

Какую пару объявлений можно поместить в // ВСТАВИТЬ ДЕКЛАРАЦИИ ЗДЕСЬ, чтобы позволить скомпилировать код? (Выберите все подходящие варианты.)

A.

ArrayList<Integer> input = null;
ArrayList<Integer> output = null;

B.

ArrayList<Integer> input = null;
List<Integer> output = null;

С

ArrayList<Integer> input = null;
List<Number> output = null;

D.

List<Number> input = null;
ArrayList<Integer> output = null;

Е.

List<Number> input = null;
List<Number> output = null;

F.

List<Integer> input = null;
List<Integer> output = null;

G. Ничего из перечисленного.

Правильные ответы даны: B, E, F и объяснение в книге гласит:
«Тип возврата определенно объявлен как List, а не ArrayList, поэтому A, D ошибаются. ......»

Это то, что я не понимаю ... почему тип возвращаемого значения ДОЛЖЕН быть только List, а не ArrayList ?? Так же, как аргумент может быть ArrayList, тогда почему возвращаемый тип также не может быть arrayList?

Спасибо

Ответы [ 8 ]

13 голосов
/ 13 мая 2009

Поскольку ArrayList является подклассом List, поэтому список, возвращаемый процессом, не обязательно будет ArrayList. Например, это может быть LinkedList.

3 голосов
/ 13 мая 2009

Тип возвращаемого значения может быть ArrayList, но он также не может быть ArrayList, это может быть LinkedList или какая-либо оболочка списка коллекций или другие вещи. Чтобы присвоить его переменной, необходимо использовать тип самого высокого уровня (это в данном случае список) или уменьшить значение, если вы знаете, что должно быть ArrayList.

На практике переменные почти всегда должны быть напечатаны как List (если не Collection). Почти никогда нет веской причины ссылаться на конкретный тип для этих классов.

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

3 голосов
/ 13 мая 2009

Это на самом деле не относится к дженерикам, но имеет дело с типами.

Простой способ думать об этом: ArrayList - это List, но List - это не обязательно ArrayList.

ArrayList реализует интерфейс List, поэтому его можно рассматривать как List. Однако, только потому, что что-то реализует List, это не ArrayList. Например, LinkedList реализует List, но не является ArrayList.

Например, допустимо следующее:

List arrayList = new ArrayList();
List linkedList = new LinkedList();

Это потому, что оба ArrayList и LinkedList оба реализуют интерфейс List, поэтому они оба могут обрабатываться как List s.

Однако следующее не разрешено:

ArrayList arrayList = new LinkedList();

Хотя и ArrayList, и LinkedList реализуют List, они не относятся к одному классу. Они могут иметь сходство, реализуя методы List, но это совершенно разные классы.

3 голосов
/ 13 мая 2009

Когда вы указываете возвращение типа List, вы говорите, что возвращаемое значение может быть любого вида List, будь то ArrayList, LinkedList или другой вид List. Если вы пытаетесь вернуть ArrayList, вы слишком конкретны - он больше не является универсальным списком.

1 голос
/ 13 мая 2009

Вы можете объявить тип возвращаемого значения ArrayList, но для этого потребуется явное приведение, например:

ArrayList output = null;
output = (ArrayList)process(input);

... что противоречит тому, что позволяют вам генерики.

1 голос
/ 13 мая 2009

Метод process не сможет решить, какую конкретную реализацию List выбрать. Его подпись дает вам обещание, что он сможет работать с ЛЮБЫМ списком (ArrayList, LinkedList и т. Д.) И может только указывать, что возвращаемое значение будет SOME List.

Например, если возвращаемый объект является LinkedList, вы не можете назначить его напрямую переменной, объявленной как ArrayList, но вы можете рассматривать его как список. Поэтому А. и Д. не верны. Вы не можете объявить переменную как ArrayList.

0 голосов
/ 14 мая 2009

Еще один способ взглянуть на это - «совместимость назначений». Семантически, вызов метода

output = process(input)

эквивалентно

nums = input;
/* body of process... */
output = returnVal; /* where process exits with "return returnVal" */

Мы знаем, что nums имеет тип List<E>, поэтому тип input должен быть "назначаемым" для List<E>. Аналогично, мы знаем, что returnVal (то есть возвращаемое значение process) типа List<E>, поэтому List<E> должно быть "присваиваемым" типу output.

Как правило, тип T «присваивается» типу U, если T является подтипом U (включая случай, когда T и U одинаковы).

Следовательно, тип input должен быть List<E> или подтипом List<E> (например, ArrayList<E> или LinkedList<E>). Аналогично, тип output должен быть List<E> или супертип из List<E> (например, Collection<E> или даже Object).

0 голосов
/ 13 мая 2009

Это потому, что в вопросе явно сказано, что метод возвращает List . Вы можете изменить определение метода, чтобы оно возвращало ArrayList , но это другой вопрос. Список может быть списком разных типов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...