Подтвердить что ты не робот

Разница между типичным типом и подстановочным типом

Я новичок в Generic, и мой вопрос: какая разница между двумя функциями:

Функция 1:

public static <E> void funct1  (List<E> list1) {

}

функция 2:

public static void funct2(List<?> list) {

}

Спасибо.

4b9b3361

Ответ 1

Первая подпись говорит: list1 - это список Es.

Вторая подпись говорит: list - это список экземпляров какого-то типа, но мы не знаем тип.

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

import java.util.List;

public class Experiment {
    public static <E> void funct1(final List<E> list1, final E something) {
        list1.add(something);
    }

    public static void funct2(final List<?> list, final Object something) {
        list.add(something); // does not compile
    }
}

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

На самом деле я нашел еще более приятную демонстрацию разницы:

public class Experiment {
    public static <E> void funct1(final List<E> list) {
        list.add(list.get(0));
    }

    public static void funct2(final List<?> list) {
        list.add(list.get(0)); // !!!!!!!!!!!!!! won't compile !!!!!!!!!
    }
}

Возможно, нам нужно <?>, когда он ограничивает только то, что мы можем с ним сделать (как @Babu_Reddy_H сделал в комментариях). Я вижу следующие преимущества шаблона:

  • Вызывающий должен знать меньше об объекте, в котором он проходит. Например, если у меня есть карта списков: Map<String, List<?>> Я могу передать свои значения вашей функции без указания типа элементов списка. Итак

  • Если я передаю объекты, параметризованные таким образом, я активно ограничиваю то, что люди знают об этих объектах и ​​что они могут с ним делать (если они держатся подальше от небезопасного кастинга).

Эти два имеют смысл, когда я их объединяю: List<? extends T>. Например, рассмотрим метод List<T> merge(List<? extends T>, List<? extends T>), который объединяет два входных списка в новый список результатов. Конечно, вы могли бы ввести еще два типа параметров, но зачем вам это нужно? Это было бы над определением вещей.

  • В конечном итоге подстановочные знаки могут иметь нижние границы, поэтому в списках вы можете использовать метод add, а get не дает вам ничего полезного. Конечно, это вызывает следующий вопрос: почему нет дженериков с нижними границами?

Для более подробного ответа см.: Когда использовать общие методы и когда использовать wild-card? и http://www.angelikalanger.com/GenericsFAQ/FAQSections/TypeArguments.html#FAQ203

Ответ 2

Generics делает коллекцию более безопасной.

List<E>: E здесь - параметр типа, который может использоваться для определения типа содержимого списка, но существует способ No проверить, что было содержанием во время runtime.

Generics are checked only during compilation time.

<? extends String>: Это было специально встроено в java, чтобы справиться с проблемой, которая была с параметром типа. "? extends String" означает, что этот список может иметь

objects which IS-A String.

Например,

Класс животных       Класс собаки расширяет животных       Класс тигра расширяет Animal

Таким образом, используя "public void go(ArrayList<Animal> a)" будет NOT accept Собака или Тигр в качестве своего содержимого, но Animal.

"public void go(ArrayList<? extends Animal> a)" необходимо сделать ArrayList take in Dog and Tiger type.

Проверьте ссылки в Head First Java.

Ответ 3

Список как параметр tuype говорит, что параметр должен быть списком элементов с любым типом объекта. Кроме того, вы можете привязать параметр E для объявления ссылок на элементы списка внутри тела функции.

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

Ответ 4

Первая - это функция, которая принимает параметр, который должен быть списком элементов типа E.

второй тип примера не определен

List<?> list

чтобы вы могли передавать список объектов любого типа.

Ответ 5

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

  • соответствует "forall E,..."
  • соответствует "существует что-то (обозначается) таким, что...."

Следовательно, обе следующие объявления обобщенного метода

function 1:

public static <E> void funct1  (List<E>; list1) {

}

function 2:

public static void funct2(List<?> list) {

}

означает, что для всех типов классов E мы определяем funct1, а для какого-либо существующего класса, обозначенного символом ?, мы dfine funct1,

Ответ 6

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

Ответ 7

В дополнение к упомянутым выше различиям также существует дополнительная разница: вы можете явно задать аргументы типа для вызова общего метода:

List<Apple> apples = ...
ClassName.<Banana>funct2(apples); // for some reason the compiler seems to be ok
                               // with type parameters, even though the method has none

ClassName.<Banana>funct1(apples); // compiler error: incompatible types: List<Apple>
                                  //                 cannot be converted to List<Banana>

(ClassName - это имя класса, содержащего методы.)