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

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

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

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

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

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

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

В каком порядке я должен использовать? В этом случае полезно использовать подстановочный знак?

4b9b3361

Ответ 1

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

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

Здесь мы имеем a List<T> shapes и a T shape, поэтому можем безопасно shapes.add(shape). Если он был объявлен List<? extends Shape>, вы не можете безопасно add к нему (потому что у вас могут быть List<Square> и a Circle).

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

Даже если вы снова не ссылаетесь на параметр ограниченного типа, параметр ограниченного типа по-прежнему требуется, если у вас есть несколько ограничений. Здесь цитата из Часто задаваемые вопросы по Java Generics Angelika Langer

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

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

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

Синтаксис

  type parameter bound     T extends Class & Interface1 & … & InterfaceN

  wildcard bound  
      upper bound          ? extends SuperType
      lower bound          ? super   SubType

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

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

Цитаты из Effective Java 2nd Edition, Item 28: Используйте ограниченные подстановочные знаки для повышения гибкости API:

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

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

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

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

Теперь мы можем addIfPretty, скажем, a Circle, на a List<Object>. Это явно типично, но наша оригинальная декларация не была достаточно гибкой, чтобы позволить ей.

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


Резюме

  • Использовать параметры ограниченного типа/подстановочные знаки, они повышают гибкость вашего API.
  • Если тип требует нескольких параметров, у вас нет выбора, кроме как использовать параметр ограниченного типа
  • Если тип требует нижнего, у вас нет выбора, кроме как использовать ограниченный шаблон
  • "Продюсеры" имеют верхние границы, "потребители" имеют нижние границы.
  • Не используйте подстановочные знаки в обратных типах

Ответ 2

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

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

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

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

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> моему методу, а затем передать полученный квадрат методу, который принимает только квадраты. Если вы использовали '?' вам нужно будет отобразить полученный Shape to Square, который не будет безопасным по типу.

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

Ответ 3

Рассмотрим следующий пример из Java Programming by James Gosling, в котором мы хотим объединить 2 SinglyLinkQueue:

public static <T1, T2 extends T1> void merge(SinglyLinkQueue<T1> d, SinglyLinkQueue<T2> s){
    // merge s element into d
}

public static <T> void merge(SinglyLinkQueue<T> d, SinglyLinkQueue<? extends T> s){
        // merge s element into d
}

Оба вышеуказанных метода имеют одинаковую функциональность. Итак, что предпочтительнее? Ответ второй. В собственных словах автора:

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

Примечание. В книге задан только второй метод и введите имя параметра S вместо "T". Первый метод отсутствует в книге.

Ответ 4

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

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

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