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 ответов:
это зависит от того, что вы нужно сделать. Вам нужно использовать параметр ограниченного типа, если вы хотите сделать что-то вроде этого:
у нас есть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
, скажем, aCircle
доList<Object>
. Это, очевидно, типобезопасно, и все же наша первоначальная декларация не была достаточно гибкой, чтобы позволить это.обзоры вопросы
- Java Generics: что такое PECS?
- может кто-нибудь объяснить, что значит
<? super T>
означает, и когда он должен быть использован и как эта конструкция должна сотрудничать с<T>
и<? extends T>
?
резюме
- используйте ограниченные параметры типа / подстановочные знаки, они повышают гибкость вашего 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'. Первый метод отсутствует в книге.
насколько я понимаю, подстановочный знак позволяет использовать более сжатый код в ситуациях, когда параметр типа не требуется (например, потому что он ссылается в нескольких местах или потому что требуется несколько границ, как подробно описано в других ответах).
в ссылке, которую вы указываете, я читаю (в разделе "Общие методы") следующие утверждения, которые намекают в этом направлении:
общие методы позволяют использовать параметры типа для выражения зависимости между типами одного или нескольких аргументов метода и / или его возвращаемый тип. Если такой зависимости нет, то общий метод не должен использоваться.
[...]
использование подстановочных знаков является более четким и кратким, чем объявление явных параметры типа, и поэтому должны быть предпочтительными, когда это возможно.
[...]
Шаблоны также имеют то преимущество, что они могут быть использованы за пределами сигнатуры методов, как и типы полей, локальные переменные и массивы.