Java: ограниченные символы подстановки или параметр ограниченного типа?

Недавно я прочитайте эту статью: http://download.oracle.com/javase/tutorial/extra/generics/wildcards.html

Мой вопрос, вместо того, чтобы создавать такой метод:

public void drawAll(List shapes){
    for (Shape s: shapes) {
        s.draw(this);
    }
}

Я могу создать такой метод, и все работает нормально:

public  void drawAll(List shapes){
    for (Shape s: shapes) {
        s.draw(this);
    }
}

Какой путь мне использовать? Полезен ли подстановочный знак в этом случае?

68
задан Eric Leschinski 1 April 2015 в 19:52
поделиться

3 ответа

Это зависит от того, что вам нужно делать. Вам нужно использовать параметр ограниченного типа, если вы хотите сделать что-то вроде этого:

public <T extends Shape> void addIfPretty(List<T> shapes, T shape) {
    if (shape.isPretty()) {
       shapes.add(shape);
    }
}

Здесь у нас есть List shape и T shape , поэтому мы можем безопасно shape.add (shape) . Если был объявлен Список , вы можете НЕ безопасно добавить к нему (потому что у вас может быть List и Circle ]).

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

Даже если вы снова не ссылаетесь на параметр ограниченного типа, параметр ограниченного типа по-прежнему требуется, если у вас несколько границ. Вот цитата из FAQs по Java Generics Анжелики Лангер

В чем разница между привязкой подстановочного знака и привязкой параметра типа?

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

Границы подстановочных знаков и границы параметров типа часто путают, потому что они оба называются границами и имеют частично похожий синтаксис. […]

Синтаксис :

  привязка параметра типа T расширяет Class & Interface1 &… & InterfaceN

подстановочный знак
верхняя граница ? расширяет SuperType
нижняя граница ? супер подтип

Подстановочный знак может иметь только одну границу: нижнюю или верхнюю. Список границ подстановочных знаков не разрешен.

Параметр типа в качестве ограничения может иметь несколько границ, но не существует такой вещи, как нижняя граница для параметра типа.

Цитаты из Эффективное 2-е издание Java, пункт 28: Используйте ограниченные символы подстановки для повышения гибкости API :

Для максимальной гибкости используйте типы подстановочных знаков во входных параметрах, которые представляют производителей или потребителей. […] PECS означает производитель- extends , потребитель- super […]

Не используйте типы подстановочных знаков в качестве возвращаемых типов . Вместо того, чтобы обеспечивать дополнительную гибкость для ваших пользователей, это заставит их использовать типы подстановочных знаков в клиентском коде. При правильном использовании типы подстановочных знаков почти не видны пользователям класса. Они заставляют методы принимать параметры, которые они должны принять, и отклонять те, которые они должны отклонять. Если пользователь класса должен подумать о типах подстановочных знаков, вероятно, что-то не так с API класса .

Применяя принцип PECS, теперь мы можем вернуться к нашему примеру addIfPretty и сделать его более гибким, написав следующее:

public <T extends Shape> void addIfPretty(List<? super T> list, T shape) { … }

Теперь мы можем addIfPretty , скажем, Обведите в List . Очевидно, что это типизированный вариант, но все же наше исходное объявление не было достаточно гибким, чтобы это разрешить.

Связанные вопросы


Резюме

  • Используются ли параметры / символы ограниченного типа, они повышают гибкость вашего API
  • Если тип требует нескольких параметров, у вас нет другого выбора, кроме как использовать параметр ограниченного типа
  • если для типа требуется нижняя граница, у вас нет другого выбора, кроме как использовать ограниченный подстановочный знак
  • «Производители» имеют верхнюю границу, «потребители» - нижнюю границу
  • Не используйте подстановочный знак в возвращаемых типах
114
ответ дан 24 November 2019 в 14:16
поделиться

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

Но если вы сделали что-то вроде:

public <T extends Shape> T drawFirstAndReturnIt(List<T> shapes){
    T s = shapes.get(0);
    s.draw(this);
    return s;
}

или как сказали polygenlubricants, если вы хотите сопоставить параметр типа в списке с другим параметром типа:

public <T extends Shape> void mergeThenDraw(List<T> shapes1, List<T> shapes2) {
    List<T> mergedList = new ArrayList<T>();
    mergedList.addAll(shapes1);
    mergedList.addAll(shapes2);
    for (Shape s: mergedList) {
        s.draw(this);
    }
}

В первом примере вы получаете немного больше безопасности типа, чем возвращаете просто Shape, поскольку затем вы можете передать результат функции, которая может принимать дочерний объект Shape. Например, вы можете передать List моему методу, а затем передать полученный Square методу, который принимает только Squares. Если вы использовали '?' вам придется преобразовать полученную форму в Square, что не будет типобезопасным.

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

5
ответ дан 24 November 2019 в 14:16
поделиться

Второй способ немного более подробный, но он позволяет вам ссылаться на T внутри него:

for (T shape : shapes) {
    ...
}

Это единственная разница, насколько я понимаю.

0
ответ дан 24 November 2019 в 14:16
поделиться
Другие вопросы по тегам:

Похожие вопросы: