Недавно я прочитал эту статью: 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);
}
}
Какой способ мне использовать? Подстановочный знак полезен в этом случае?
java
api
generics
bounded-wildcard
Тони Ле
источник
источник
Ответы:
Это зависит от того, что вам нужно делать. Вам нужно использовать параметр ограниченного типа, если вы хотите сделать что-то вроде этого:
public <T extends Shape> void addIfPretty(List<T> shapes, T shape) { if (shape.isPretty()) { shapes.add(shape); } }
Здесь у нас есть a
List<T> shapes
и aT shape
, поэтому мы можем смелоshapes.add(shape)
. Если он был объявленList<? extends Shape>
, вы НЕ можете безопасноadd
перейти к нему (потому что у вас могут быть aList<Square>
и aCircle
).Таким образом, давая имя параметру ограниченного типа, у нас есть возможность использовать его в другом месте нашего универсального метода. Конечно, эта информация не всегда требуется, поэтому, если вам не нужно так много знать о типе (например, ваш
drawAll
), тогда достаточно подстановочного знака.Даже если вы снова не ссылаетесь на параметр ограниченного типа, параметр ограниченного типа по-прежнему требуется, если у вас есть несколько границ. Вот цитата из FAQs Анжелики Лангер по Java Generics
Цитаты из Effective Java 2nd Edition, пункт 28: Используйте ограниченные символы подстановки для повышения гибкости API :
Применяя принцип PECS, мы можем теперь вернуться к нашему
addIfPretty
примеру и сделать его более гибким, написав следующее:public <T extends Shape> void addIfPretty(List<? super T> list, T shape) { … }
Теперь мы можем
addIfPretty
, скажем, от aCircle
до aList<Object>
. Это очевидно типизировано, но все же наше исходное объявление не было достаточно гибким, чтобы позволить это.Связанные вопросы
<? super T>
означает и когда его следует использовать, и как эта конструкция должна взаимодействовать с<T>
и<? extends T>
?Резюме
источник
reverse(List<?>)
пример из JLS java.sun.com/docs/books/jls/third_edition/html/...?
как параметр метода, например,doWork(? type)
потому что тогда вы не можете использовать этот параметр в методе. Вам нужно использовать параметр типа.В вашем примере вам действительно не нужно использовать 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>
мой метод, а затем передать полученный Square методу, который принимает только Squares. Если вы использовали '?' вам придется преобразовать полученную форму в Square, что не будет типобезопасным.Во втором примере вы гарантируете, что оба списка имеют один и тот же параметр типа (чего нельзя сделать с '?', Поскольку каждый '?' Отличается), чтобы вы могли создать список, содержащий все элементы из них обоих. .
источник
Shape s = ...
должен бытьT s = ...
, иначеreturn s;
не должен компилироваться.Рассмотрим следующий пример из 4-го издания The 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». Первого метода в книге нет.
источник
Насколько я понимаю, подстановочный знак позволяет использовать более сжатый код в ситуациях, когда параметр типа не требуется (например, потому что он упоминается в нескольких местах или потому, что требуется несколько границ, как подробно описано в других ответах).
В указанной вами ссылке я прочитал (в разделе «Общие методы») следующие утверждения, которые намекают в этом направлении:
источник
Второй способ более подробный, но позволяет ссылаться
T
внутри него:for (T shape : shapes) { ... }
Это единственная разница, насколько я понимаю.
источник