Java Generics: список, список <Object>, список <?> - PullRequest
70 голосов
/ 29 января 2009

Может кто-нибудь объяснить как можно подробнее, различия между следующими типами?

List
List<Object>
List<?>

Позвольте мне сделать это более конкретным. Когда бы я хотел использовать

// 1 
public void CanYouGiveMeAnAnswer(List l) { }

// 2
public void CanYouGiveMeAnAnswer(List<Object> l) { }

// 3
public void CanYouGiveMeAnAnswer(List<?> l) { }

Ответы [ 12 ]

101 голосов
/ 29 января 2009

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

Позвольте мне ответить на ваши вопросы функционально (если это не плохое слово для ОО-дискуссий).

До появления дженериков у вас были старые добрые конкретные классы, такие как Vector.

Vector V = new Vector();

Векторы содержат любой старый объект, который вы им даете.

V.add("This is an element");
V.add(new Integer(2));
v.add(new Hashtable());

Однако они делают это путем приведения всего, что вы ему даете, в Object (корень всех классов Java). Это нормально, пока вы не попытаетесь получить значения, хранящиеся в вашем векторе. Когда вы это сделаете, вам нужно привести значение обратно в исходный класс (если вы хотите сделать что-то значимое с ним).

String s = (String) v.get(0);
Integer i = (Integer) v.get(1);
Hashtable h = (Hashtable) v.get(2);

Кастинг быстро устареет. Более того, компилятор жалуется на непроверенные приведения. Для яркого примера этого используйте библиотеку XML-RPC от Apache (в любом случае, версия 2). Самая важная проблема с этим заключается в том, что потребители вашего Vector должны знать точный класс его значений во время время компиляции , чтобы правильно разыграть. В случаях, когда производитель Вектор и потребитель полностью изолированы друг от друга, это может быть фатальной проблемой.

Введите дженерики. Дженерики пытаются создать строго типизированные классы для выполнения общих операций.

ArrayList<String> aList = new ArrayList<String>();
aList.add("One");
String element = aList.get(0); // no cast needed
System.out.println("Got one: " + element); 

Теперь, если вы взглянете на печально известную банду из книги *1022* о четверке, вы заметите, что есть некоторая мудрость в отрыве переменных от их реализующего класса. Лучше думать о контрактах, чем о реализации. Таким образом, вы могли бы сказать, что все объекты List делают одно и то же: add(), get(), size() и т. Д. Однако существует много реализаций операций List, которые могут по-разному подчиняться контракту (например, ArrayList). Тем не менее, тип данных, с которыми имеет дело этот объект, оставлен на усмотрение пользователя-пользователя универсального класса. Сложите все это вместе, и вы очень часто увидите следующую строку кода:

List<String> L = new ArrayList<String>();

Вы должны прочитать это как "L - это вид List, который имеет дело с объектами String". Когда вы начинаете работать с классами Factory, важно иметь дело с контрактами, а не с конкретными реализациями. Фабрики производят объекты различных типов во время выполнения.

Использование дженериков довольно просто (большую часть времени). Однако в один ужасный день вы можете решить, что хотите реализовать универсальный класс. Возможно, вы подумали о новой реализации List. Когда вы определяете этот класс, вы используете <t> в качестве заполнителя для типа объекта, которым будут управлять методы. Если вы не уверены, используйте общие классы для List, пока не почувствуете себя комфортно. Затем вы можете погрузиться в реализацию с большей уверенностью. Или вы можете посмотреть исходный код для различных классов List, которые поставляются с JRE. С открытым исходным кодом это здорово.

Посмотрите на Oracle / Sun документы о дженериках . Приветствия.

40 голосов
/ 29 января 2009

Моими простыми словами:

Список

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

Список

Создает список, который может содержать любой тип объекта, но может получить только другой Список

Например, это не работает;

List<Object> l = new ArrayList<String>();

Конечно, вы можете добавлять что угодно, но только можете тянуть Объект.

List<Object> l = new ArrayList<Object>();

l.add( new Employee() );
l.add( new String() );

Object o = l.get( 0 );
Object o2 = l.get( 1 );

Наконец

List <?>

Позволит вам назначить любой тип, в том числе

List <?> l = new ArrayList(); 
List <?> l2 = new ArrayList<String>();

Это будет называться набором неизвестно , и поскольку общий знаменатель неизвестно - это объект, вы сможете получить объекты (совпадение)

Важность unknown возникает, когда он используется с подклассами:

List<? extends Collection> l = new ArrayList<TreeSet>(); // compiles

List<? extends Collection> l = new ArrayList<String>(); // doesn't,
// because String is not part of *Collection* inheritance tree. 

Надеюсь, использование Collection как типа не создает путаницы, это было единственное дерево, которое пришло мне в голову.

Разница в том, что l - это коллекция неизвестных , принадлежащая иерархии Collection .

15 голосов
/ 10 марта 2012

Чтобы добавить к уже хорошим ответам здесь:

Аргументы метода:

List<? extends Foo>

хороший выбор, если вы не намереваетесь изменять список, а заботитесь только о том, чтобы все в списке можно было вводить для типа «Foo». Таким образом, вызывающая сторона может передать List , и ваш метод работает. Обычно лучший выбор.

List<Foo>

хороший выбор, если вы собираетесь добавить объекты Foo в список в вашем методе. Вызывающий не может передать список , так как вы намерены добавить Foo в список.

List<? super Foo>

хороший выбор, если вы намереваетесь добавить объекты Foo в список, и неважно, что еще находится в списке (т. Е. У вас все в порядке, получается список , который содержит 'Dog', который не имеет ничего общего с Foo).

Возвращаемые значения метода

точно так же, как аргументы метода, но с обратными преимуществами.

List<? extends Foo>

Гарантирует, что все в возвращаемом списке имеет тип 'Foo'. Это может быть список . Абонент не может добавить в список. Это ваш выбор и самый распространенный случай на сегодняшний день.

List<Foo>

Так же, как список <? расширяет Foo>, но также позволяет вызывающей стороне добавлять в список. Менее распространенный.

List<? super Foo>

позволяет вызывающей стороне добавлять объекты Foo в список, но не гарантирует, что будет возвращено из list.get (0) ... это может быть что угодно от Foo до Object. Единственная гарантия состоит в том, что это не будет список «Dog» или какой-либо другой вариант, который помешал бы list.add (foo) быть законным. Очень редкий случай использования.

Надеюсь, это поможет. Удачи!

пс. Подводя итог ... два вопроса ...

Вам нужно добавить в список? Вас волнует, что в списке?

да да - используйте Список .

да нет - использовать Список <? супер фу>.

нет да - использовать <? расширяет Foo> --- чаще всего.

нет нет - используйте <?>.

15 голосов
/ 29 января 2009

Я отсылаю вас к превосходному учебнику по Java Generics и "расширенному" учебнику Generics , которые доступны от Sun Microsystems. Другой замечательный ресурс - книга Generics и коллекции Java .

4 голосов
/ 27 октября 2015

Я постараюсь ответить на это подробно. До дженериков у нас был только List (необработанный список), и он мог содержать практически все, что мы могли придумать.

List rawList = new ArrayList();
rawList.add("String Item");
rawList.add(new Car("VW"));
rawList.add(new Runnable() {
            @Override
            public void run() {
               // do some work.
            }
        });

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

   Object item = rawList.get(0); // we get object without casting.
   String sameItem = (String) rawList.get(0); // we can use casting which may fail at runtime.

Таким образом, можно сделать вывод, что List может хранить объект (почти все это объекты в Java) и всегда возвращает объект.

Дженерики

Теперь давайте поговорим о дженериках. Рассмотрим следующий пример:

List<String> stringsList = new ArrayList<>();
stringsList.add("Apple");
stringsList.add("Ball");
stringsList.add(new Car("Fiat")); //error
String stringItem = stringsList.get(0);

В приведенном выше случае мы не можем вставить ничего, кроме String в stringsList, поскольку компилятор Java применяет строгую проверку типов к универсальному коду и выдает ошибки, если код нарушает безопасность типов. И мы получаем ошибку, когда пытаемся вставить в нее экземпляр Car. Также он исключает приведение, поскольку вы можете проверить, когда мы invoke получаем метод. Проверьте эту ссылку для понимания , почему мы должны использовать дженерики .

List<Object>

Если вы прочтете об удалении типа, то поймете, что List<String>, List<Long>, List<Animal> и т. Д. Будут иметь разные статические типы во время компиляции, но будут иметь одинаковый динамический тип List во время выполнения.

Если у нас есть List<Object>, тогда он может хранить только Object в нем, и почти все это Object в Java. Таким образом, мы можем иметь:

 List<Object> objectList = new ArrayList<Object>();
 objectList.add("String Item");
 objectList.add(new Car("VW"));
 objectList.add(new Runnable() {
        @Override
        public void run() {

        }
 });
 Object item = objectList.get(0); // we get object without casting as list contains Object
 String sameItem = (String) objectList.get(0); // we can use casting which may fail at runtime.

Кажется, List<Object> и List одинаковы, но на самом деле это не так. Рассмотрим следующий случай:

List<String> tempStringList = new ArrayList<>();
rawList = tempStringList; // Ok as we can assign any list to raw list.
objectList = tempStringList; // error as List<String> is not subtype of List<Obejct> becuase generics are not convariant.

Вы можете видеть, что мы можем назначить любой список необработанному списку, и основной причиной этого является обеспечение обратной совместимости. Кроме того, List<String> будет преобразовано в List во время выполнения из-за стирания типа, и назначение все равно будет в порядке.

Но List<Object> означает, что он может ссылаться только на список объектов, а также может хранить только объекты. Даже если String является подтипом Object, мы не можем присвоить List<String> List<Object>, поскольку генерики не являются ковариантными, как массивы. Они инвариантны. Также проверьте эту ссылку для получения дополнительной информации. Также проверьте разницу между List и List<Object> в этом вопросе .

List<?>

Теперь у нас осталось List<?>, что в основном означает список неизвестного типа и может ссылаться на любой список.

List<?> crazyList = new ArrayList<String>();
 List<String> stringsList = new ArrayList<>();
 stringsList.add("Apple");
 stringsList.add("Ball");
 crazyList = stringsList; // fine

Символ ? известен как подстановочный знак, а List<?> - это список неограниченных подстановочных знаков. Есть определенные моменты, которые нужно соблюдать сейчас.

Мы не можем создать экземпляр этого списка, так как следующий код не скомпилируется:

List<?> crazyList = new ArrayList<?>(); // any list.

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

List<?> crazyList2 = new ArrayList<String>();

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

crazyList2.add("Apple"); // error as you dont actually know what is that type.

Теперь возникает вопрос, когда бы я хотел использовать List<?>?

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

 public static void print(List<?> list){
        System.out.println(list);
    }

Вы также можете проверить разницу между List, List<?>, List<T>, List<E>, and List<Object> здесь .

4 голосов
/ 29 января 2009

Самое простое объяснение, которое не является "RTFM":

List

Будет генерировать много предупреждений компилятора, но в основном эквивалентно:

List<Object>

В то время как:

List<?>

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

List<? extends SomeOtherThing>
3 голосов
/ 29 января 2009

Самое короткое объяснение: Второй элемент - это список, который может содержать любой тип, и вы можете добавлять к нему объекты:

List<Object>

Первый элемент, который вы перечисляете, считается по существу эквивалентным этому, за исключением того, что вы получите предупреждения компилятора, потому что это «необработанный тип».

List

Третий - список, который может содержать любой тип, но вы ничего не можете добавить к нему:

List<?> 

По сути, вы используете вторую форму (List<Object>), когда у вас действительно есть список, который может содержать любой объект, и вы хотите иметь возможность добавлять элементы в список. Вы используете третью форму (List<?>), когда получаете список как возвращаемое значение метода, и вы будете перебирать список, но никогда ничего не добавляете в него. Никогда не используйте первую форму (List) в новом коде, компилируемом под Java 5. или позже.

1 голос
/ 29 января 2009

Я бы сказал так: хотя List и List<Object> могут содержать объекты любого типа, List<?> содержит элементы неизвестного типа, но как только этот тип захвачен, он может содержать только элементы этого типа. тип. Вот почему это единственный типобезопасный вариант этих трех, и, следовательно, как правило, предпочтительнее.

0 голосов
/ 11 октября 2013

List предназначен для передачи параметра входного типа объекта. Пока список <? > представляет тип шаблона. Подстановочный знак <? > имеет неизвестный тип параметра. Подстановочный знак не может использоваться в качестве аргумента типа для универсального метода и не может использоваться для создания универсального экземпляра класса. Подстановочный знак может использоваться для расширения класса подтипа, List <? расширяет номер>. Чтобы ослабить ограничение типа объекта и в этом случае ослабить тип объекта «Число».

0 голосов
/ 18 июля 2012

List, List<?>, and List<? extends Object> это одно и то же. Второй более явный. Для списка этого типа вы не можете знать, какие типы допустимы для него, и вы ничего не знаете о типах, которые вы можете из него получить, за исключением того, что они будут объектами.

List<Object> конкретно означает, что список содержит объекты любого типа.

Допустим, мы составили список Foo:

List<Foo> foos= new ArrayList<Foo>();

Не разрешено ставить Bar в foos.

foos.add(new Bar()); // NOT OK!

Всегда допустимо помещать что-либо в List<Object>.

List<Object> objs = new ArrayList<Object>();
objs.add(new Foo());
objs.add(new Bar());

Но вам нельзя позволять ставить Bar в List<Foo> - вот и весь смысл. Значит, это так:

List<Object> objs = foos; // NOT OK!

не законно.

Но можно сказать, что foos - это список чего-то, но мы не знаем точно, что это:

List<?> dontKnows = foos;

Но тогда это означает, что это должно быть запрещено идти

dontKnows.add(new Foo()); // NOT OK
dontKnows.add(new Bar()); // NOT OK

потому что переменная dontKnows не знает, какие типы допустимы.

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