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);
    }
}

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

5 59

5 ответов:

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

public <T extends Shape> void addIfPretty(List<T> shapes, T shape) {
    if (shape.isPretty()) {
       shapes.add(shape);
    }
}
у нас есть List<T> shapes и T shape, поэтому мы можем смело shapes.add(shape). Если он был объявлен List<? extends Shape> вы можете не безопасное add к нему (потому что у вас может быть List<Square> и Circle).

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

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

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

шаблон может иметь только один предел, в то время как параметр типа может иметь несколько границ. Шаблон может иметь нижний или верхний предел, в то время как нет такой вещи, как нижняя граница для параметра типа.

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

синтаксис:

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

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

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

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

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

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

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

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

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

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

обзоры вопросы


резюме

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

в вашем примере вам действительно не нужно использовать 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);
    }
}

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

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

рассмотрим следующий пример из Java-программирования Джеймса Гослинга 4-го издания ниже, где мы хотим объединить 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
}

оба вышеуказанных метода имеют одинаковую функциональность. Так что же предпочтительнее? Ответ-2-й. По собственным словам автора:

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

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

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

в ссылке, которую вы указываете, я читаю (в разделе "Общие методы") следующие утверждения, которые намекают в этом направлении:

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

[...]

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

[...]

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

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

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

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