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

Я читаю главу об обобщениях в эффективной Java.

Помогите мне понять разницу между Set, Set<?> и Set<Object>?

Следующий абзац взят из книги.

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

Что имеется в виду под «каким-то неизвестным типом»? Все ли неизвестные типы относятся к типу Object? В таком случае, в чем конкретная разница между Set<?> и Set<Object>?


person Vinoth Kumar C M    schedule 09.09.2011    source источник


Ответы (8)


  • необработанный тип (Set) обрабатывает тип так, как если бы у него вообще не было информации об универсальном типе. Обратите внимание на тонкий эффект, заключающийся в том, что игнорируется не только аргумент типа T, но и все другие аргументы типа, которые могут иметь методы этого типа. Вы можете добавить к нему любое значение, и оно всегда будет возвращать Object.
  • Set<Object> – это Set, который принимает все объекты Object (т. е. все объекты) и возвращает объекты типа Object.
  • Set<?> – это Set, который принимает все объекты определенного, но неизвестного типа и возвращает объекты этого типа. Поскольку ничего не известно об этом типе, вы не можете ничего добавить в этот набор (кроме null), и единственное, что вы знаете о возвращаемых им значениях, это то, что они являются подтипом Object.
person Joachim Sauer    schedule 09.09.2011
comment
Так является ли необработанный тип (set) таким же, как набор, который принимает Object типы (Set <Object>)? - person orrymr; 25.07.2016
comment
@orrymr: это будет работать очень похоже, но явно не совместимо с присваиванием (и некоторые крайние случаи на самом деле разные). Кроме того, технически это не всегда Object, а нижняя граница универсального типа (т.е. если универсальный тип просто <T>, то это Object, но если универсальный тип <T extends Number>, то нижняя граница Number). - person Joachim Sauer; 25.07.2016
comment
Несовместимо с назначением, поэтому нельзя было сказать: Set <Object> mySet = new Set <Integer>; ? Не связано ли это с тем, что Set <Integer> не наследуется от Set <Object>? - person orrymr; 25.07.2016

Во время выполнения JVM просто увидит Set из-за стирания типа.

Во время компиляции есть разница:

Set<Object> параметризовал тип E с помощью Object, таким образом, Set.add(E element) будет параметризован до Set.add(Object element).

Set<?>, с другой стороны, добавляет подстановочный знак к типу E, поэтому Set.add(E element) преобразуется в Set.add(? element). Поскольку это не компилируется, java вместо этого «переводит» его как Set.add(null element). Это означает, что вы не можете ничего добавить к этому набору (кроме нуля). Причина в том, что подстановочный знак ссылается на неизвестный тип.

person Buhake Sindi    schedule 09.09.2011
comment
Хотя Set‹Object› параметризован как Set.add (элемент объекта), невозможно добавить подтип объекта (например, строку), верно? - person Vinoth Kumar C M; 09.09.2011
comment
Это возможно, поскольку все конкретные классы неявно/явно расширяют Object. - person Buhake Sindi; 09.09.2011
comment
Так почему же этот код не компилируется ?Set‹Object› set = new HashSet‹Integer›(); - person Vinoth Kumar C M; 09.09.2011
comment
Потому что вы создали свой набор неправильно. Экземпляр set должен иметь тот же тип T, который вы назначили. Правильно делать Set<Object> set = new HashSet<Object>();. Ваш можно интерпретировать как Set<T> set = new HashSet<U>() (T != U в терминах Generics). - person Buhake Sindi; 09.09.2011

что значит "какой-то неизвестный тип"

Что именно это означает: у Set есть какой-то общий параметр, но мы не знаем, что это такое.

Таким образом, набор, присвоенный переменной Set<?>, может быть Set<String>, или Set<Integer>, или Set<Map<Integer, Employee>>, или набором, содержащим любой другой конкретный тип.

Итак, что это значит для того, как вы можете его использовать? Что ж, все, что вы получите из этого, будет экземпляром ?, что бы это ни было. Поскольку мы не знаем, что такое параметр типа, вы не можете сказать ничего более конкретного, чем то, что элементы набора будут присваиваться Object (только потому, что все классы наследуются от него).

И если вы думаете о добавлении чего-то в набор — ну, метод add принимает ? (что имеет смысл, так как это тип объектов в наборе). Но если вы попытаетесь добавить какой-либо конкретный объект, как вы можете быть уверены, что это безопасно для типов? Вы не можете - если вы вставляете строку, вы можете поместить ее, например, в Set<Integer>, что нарушит безопасность типов, которую вы получаете от дженериков. Таким образом, пока вы не знаете тип универсального параметра, вы не можете предоставить какие-либо аргументы этого типа (за единственным исключением null, так как это «экземпляр» любого типа).


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

person Andrzej Doyle    schedule 09.09.2011

Set: Здесь нет дженериков, это небезопасно. Добавьте то, что вы хотите.

Set<?>: Набор определенного типа, который мы не знаем из нашей области. То же, что Set<? extends Object>. Может ссылаться на наборы любого типа, но этот тип должен быть определен в точке фактического создания экземпляра набора. С помощью ссылки с подстановочными знаками мы не можем изменить набор (мы не можем добавить или удалить что-либо, кроме null). Это как вид.

Set<Object>: набор, содержащий объекты (только базовый класс, а не подклассы). Я имею в виду, что вы можете создать экземпляр набора, используя коллекции типа Object, например HashSet<Object>, но не HashSet<String>. Можно конечно добавлять в набор элементы любого типа, но просто так бывает, что все является Объектом или подклассом Объекта. Если набор был определен как Набор, вы можете добавлять только Числа и подклассы Числа, и ничего больше.

person Mister Smith    schedule 09.09.2011
comment
Set<Object> может определенно содержать любой подкласс Object, вы можете добавить к нему Integer, String и все остальное. - person Joachim Sauer; 09.09.2011
comment
Так почему же этот код не компилирует ?Set‹Object› set = new HashSet‹Integer›() - person Vinoth Kumar C M; 09.09.2011
comment
@cmv Потому что вы определяете ссылку на набор как набор типа объекта и ничего более. Вы должны были написать Set<? extends Object> для компиляции. Обратите внимание, что с массивами можно написать Object[] arr = new Integer[3];. - person Mister Smith; 09.09.2011
comment
@cmv причина, по которой компилятор не позволяет вам присвоить HashSet<Integer> Set<Object> или даже HashSet<Object>, заключается в том, что, удерживая эту ссылку, вы добавите что-то, что не является целым числом. - person Mister Smith; 09.09.2011

Разница между Set<Object> и Set<?> заключается в том, что переменной типа Set<?> может быть назначен более конкретный общий тип, например:

Set<?> set = new HashSet<Integer>();

в то время как Set<Object> можно назначить только Set<Object>:

Set<Object> set = new HashSet<Integer>(); // won't compile

Set<Object> по-прежнему полезен, так как в него можно поместить любой объект. В этом смысле он очень похож на необработанный Set, но лучше работает с общей системой типов.

person Avi    schedule 09.09.2011

Я объяснял этот пункт своему другу и специально попросил метод «safeAdd» в качестве противодействия примеру unsafeAdd. Итак, вот оно.

public static void main(String[] args) {
    List<String> strings = new ArrayList<String>();

    unsafeAdd(strings, new Integer(42)); // No compile time exceptions

    // New 
    safeAdd(strings, new Integer(42)); // Throwing an exception at compile time


    String s = strings.get(0); // Compiler-generated cast

}

private static void unsafeAdd(List list, Object o) {
    list.add(o);
}


private static <E> void safeAdd(List<E> list, E o) {
    list.add(o);
}
person th3byrdm4n    schedule 08.01.2014

Set<?> set = new HashSet<String>();
set.add(new Object()); // compile time error

Поскольку мы не знаем, что обозначает тип элемента множества, мы не можем добавлять к нему объекты. Метод add() принимает аргументы типа E, типа элемента Set. Когда фактический параметр типа равен ?, он обозначает какой-то неизвестный тип. Любой параметр, который мы передаем для добавления, должен быть подтипом этого неизвестного типа. Поскольку мы не знаем, что это за тип, мы не можем ничего передать. Единственным исключением является null, который является членом каждого типа.

Получив Set<?>, мы можем вызвать get() и использовать результат. Тип результата — неизвестный тип, но мы всегда знаем, что это объект. Поэтому безопасно присвоить результат get() переменной типа Object или передать его в качестве параметра там, где ожидается тип Object.

person Narendra Yadala    schedule 09.09.2011

Скажем, вы пишете общий метод для печати элементов, появляющихся в списке. Теперь этот метод можно использовать для печати списков типа Integer, Double, Object или любого другого типа. Какой из них вы выбираете?

  1. List<Object> : Если мы воспользуемся этим, это поможет нам напечатать только элементы типа Object. Это не будет полезно для печати элементов, принадлежащих другим классам, таким как Double. Это связано с тем, что Generic не поддерживает наследование по умолчанию и требует явного указания с помощью ключевого слова super.

    // Would only print objects of type 'Object'
    
    public static void printList(List<Object> list) {
        for (Object elem : list)
            System.out.println(elem + " ");
        System.out.println();
    }
    
  2. List<?>: Это могло бы помочь нам иметь общий метод для печати любого типа данных. Мы могли бы использовать этот метод для печати экземпляров любого типа.

    //  The type would really depend on what is being passed
    public static void printList(List<?> list) {
        for (Object elem: list)
            System.out.print(elem + " ");
        System.out.println();
    }
    
person Subodh Karwa    schedule 24.09.2016