Java Generics: List, List <Object>, List <?>

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

List
List<Object>
List<?>

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

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

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

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

Ответ 1

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

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

До появления дженериков существовали конкретные классы, такие как 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);

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

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

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

Книга "Шаблоны проектирования" побуждает читателя думать с точки зрения договоров, а не конкретных типов. Есть мудрость (и повторное использование кода) в отделении переменных от их реализующего класса.

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

Сложите все это вместе, и вы часто будете видеть следующие виды кода:

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

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

Использовать дженерики довольно просто (большую часть времени).

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

Если вы все еще в замешательстве, используйте универсальные классы для List, пока не почувствуете себя комфортно. Позже вы можете погрузиться в реализацию с большей уверенностью. Или вы можете посмотреть исходный код для различных классов List, которые поставляются с JRE. С открытым исходным кодом это здорово.

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

Ответ 2

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

Список

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

Список < Объект >

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

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

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

Конечно, вы можете добавить что угодно, но только можете вытащить Object.

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 <?> l = new ArrayList(); 
List <?> l2 = new ArrayList<String>();

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

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

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.

Ответ 4

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

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

List<? extends Foo>

Хороший выбор, если вы не собираетесь изменять список, и только заботитесь, чтобы все в списке было назначено для типа "Foo". Таким образом, вызывающий может передать список <FooSubclass> и ваш метод работает. Обычно лучший выбор.

List<Foo>

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

List<? super Foo>

хороший выбор, если вы намерены добавить в список объекты Foo, и не важно, что еще в списке (т.е. вы нормально получаете List <Object> , который содержит "Собака", которая не имеет ничего общего с Foo).

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

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

List<? extends Foo>

Гарантирует, что все в возвращаемом списке имеет тип "Foo". Это может быть List <FooSubclass> хоть. Caller не может добавить в список. Это ваш выбор и самый распространенный случай.

List<Foo>

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

List<? super Foo>

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

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

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

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

да да - используйте List <Foo> .

да нет - используйте List <? super Foo > .

no yes - использовать <? расширяет Foo > --- наиболее распространенный.

no no - использование < > .

Ответ 5

Простейшее объяснение, которое не является "RTFM":

List

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

List<Object>

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

List<?>

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

List<? extends SomeOtherThing>

Ответ 6

Я попытаюсь ответить на это подробно. До дженериков у нас был только 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 получаем метод. Проверьте эту ссылку для понимания почему мы должны использовать generics.

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> в этом question.

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> здесь.

Ответ 7

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

List<Object>

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

List

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

List<?> 

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

Ответ 8

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

Ответ 9

В дополнение к учебникам, упомянутым Робом, здесь wikibook объясняет тему:
http://en.wikibooks.org/wiki/Java_Programming/Generics


Edit:

  • Нет ограничений на тип элементов в списке

  • Элементы в списке должны расширять Object

  • Подстановочный знак, используемый сам по себе, поэтому он соответствует чему-либо

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

Ответ 10

Когда я хочу использовать

public void CanYouGiveMeAnAnswer( List l ){}

Когда вы не можете сделать все свое литье.

Когда я хочу использовать

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

Если вы хотите ограничить тип списка. Например, это будет недопустимым аргументом.

 new ArrayList<String>();

Когда я хочу использовать

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

В основном никогда.

Ответ 11

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 не знает, какие типы являются законными.

Ответ 12

Список <Object> предназначен для передачи параметра типа ввода объекта. Хотя List <? > обозначает тип подстановочных знаков. Подстановочный знак <? > имеет тип Неизвестный параметр. Подстановочный знак нельзя использовать в качестве аргумента типа для общего метода и не может использоваться для создания общего экземпляра класса. Подстановочный знак можно использовать для расширения класса подтипа, List <? extends Number > . Чтобы уменьшить ограничение типа объекта и в этом случае уменьшить тип объекта "Число".