Я читаю об общих методах из OracleDocGenericMethod . Меня очень смущает сравнение, когда в нем говорится, когда использовать подстановочные знаки, а когда - общие методы. Цитата из документа.
interface Collection<E> { public boolean containsAll(Collection<?> c); public boolean addAll(Collection<? extends E> c); }
Вместо этого мы могли бы использовать общие методы:
interface Collection<E> { public <T> boolean containsAll(Collection<T> c); public <T extends E> boolean addAll(Collection<T> c); // Hey, type variables can have bounds too! }
[…] Это говорит нам, что аргумент типа используется для полиморфизма; его единственный эффект - позволить использовать множество фактических типов аргументов на разных сайтах вызова. В таком случае следует использовать подстановочные знаки. Подстановочные знаки предназначены для поддержки гибкого выделения подтипов, что мы и пытаемся здесь выразить.
Разве мы не думаем, что wild card вроде (Collection<? extends E> c);
тоже поддерживает своего рода полиморфизм? Тогда почему использование универсальных методов в этом случае считается нецелесообразным?
Далее говорится:
Универсальные методы позволяют использовать параметры типа для выражения зависимостей между типами одного или нескольких аргументов метода и / или его возвращаемого типа. Если такой зависимости нет, не следует использовать общий метод.
Что это значит?
Они представили пример
class Collections { public static <T> void copy(List<T> dest, List<? extends T> src) { ... }
[...]
Мы могли бы написать сигнатуру этого метода другим способом, вообще без использования подстановочных знаков:
class Collections { public static <T, S extends T> void copy(List<T> dest, List<S> src) { ... }
Документ не одобряет второе объявление и способствует использованию первого синтаксиса? В чем разница между первым и вторым объявлением? Кажется, оба делают одно и то же?
Может кто-нибудь зажечь эту зону.
?
. Вы можете переписать его как `public static <T1 extends Number, T2 extends Number> void copy (List <T1> dest, List <T2> src), и в этом случае станет очевидно, что происходит.List
параметре типа using.List<T super Integer>
недействителен и не будет компилироваться.<T extends X & Y>
-> несколько границ.Рассмотрим следующий пример из 4-го издания The Java Programming by James Gosling ниже, где мы хотим объединить 2 SinglyLinkQueue:
Оба вышеуказанных метода имеют одинаковую функциональность. Итак, что предпочтительнее? Ответ второй. По словам автора:
«Общее правило - использовать подстановочные знаки, когда это возможно, потому что код с подстановочными знаками обычно более читабелен, чем код с несколькими параметрами типа. Решая, нужна ли вам переменная типа, спросите себя, используется ли эта переменная типа для связи двух или более параметров, или связать тип параметра с типом возвращаемого значения. Если ответ отрицательный, то подстановочного знака должно быть достаточно ".
Примечание. В книге дается только второй метод, а имя параметра типа - S вместо «T». Первого метода в книге нет.
источник
В вашем первом вопросе: это означает, что если существует связь между типом параметра и типом возвращаемого значения, используйте общий.
Например:
Здесь вы извлекаете некоторые из T по определенным критериям. Если T -
Long
ваши методы вернутLong
иCollection<Long>
; фактический тип возвращаемого значения зависит от типа параметра, поэтому полезно и рекомендуется использовать универсальные типы.Если это не так, вы можете использовать типы подстановочных знаков:
В этих двух примерах, независимо от типа элементов в коллекциях, будут возвращаться типы
int
иboolean
.В ваших примерах:
эти две функции будут возвращать логическое значение независимо от типа элементов в коллекциях. Во втором случае это ограничено экземплярами подкласса E.
Второй вопрос:
Этот первый код позволяет передавать
List<? extends T> src
в качестве параметра неоднородный . Этот список может содержать несколько элементов разных классов, если все они расширяют базовый класс T.если у тебя есть:
и
ты мог бы сделать
С другой стороны
ограничиваться
List<S> src
одним конкретным классом S, который является подклассом T. Список может содержать только элементы одного класса (в этом экземпляре S) и никакого другого класса, даже если они также реализуют T. Вы не сможете использовать мой предыдущий пример, но можете:источник
List<? extends Fruit> basket = new ArrayList<? extends Fruit>();
Это недопустимый синтаксис. Вы должны создать экземпляр ArrayList без ограничений.ArrayList(Collection<? extends E> c)
. Вы можете мне объяснить, почему вы так сказали?Метод подстановочных знаков также является универсальным - вы можете вызывать его с некоторым диапазоном типов.
<T>
Синтаксис определяет имя переменной типа. Если переменная типа имеет какое-либо использование (например, в реализации метода или в качестве ограничения для другого типа), тогда имеет смысл назвать ее, иначе вы можете использовать ее?
как анонимную переменную. Итак, похоже, это просто сокращение.Более того,
?
при объявлении поля нельзя избежать синтаксиса:источник
Я постараюсь ответить на ваш вопрос один за другим.
Нет. Причина в том, что ограниченный подстановочный знак не имеет определенного типа параметра. Это неизвестно. Все, что он «знает», - это то, что «сдерживание» относится к определенному типу
E
(независимо от того, что определено). Таким образом, он не может проверить и обосновать, соответствует ли предоставленное значение ограниченному типу.Таким образом, нет смысла иметь полиморфное поведение для подстановочных знаков.
Первый вариант в этом случае лучше, поскольку
T
он всегда ограничен иsource
определенно будет иметь значения (неизвестных), которые являются подклассамиT
.Итак, предположим, что вы хотите скопировать весь список чисел, первый вариант будет
src
, По существу, может принятьList<Double>
,List<Float>
и т.д. , как есть верхняя граница к параметризованному типу найден вdest
.Второй вариант заставит вас выполнить привязку
S
для каждого типа, который вы хотите скопировать, напримерКак
S
параметризованный тип, требующий привязки.Надеюсь, это поможет.
источник
<S extends T>
утверждает, чтоS
это параметризованный тип, являющийся подклассомT
, поэтому для него требуется параметризованный тип (без подстановочных знаков), являющийся подклассомT
.Еще одно отличие, которое здесь не указано.
Но следующее приведет к ошибке времени компиляции.
источник
Насколько я понимаю, существует только один вариант использования, когда подстановочный знак строго необходим (т.е. может выражать то, что вы не можете выразить с помощью явных параметров типа). Это когда вам нужно указать нижнюю границу.
Помимо этого, однако, подстановочные знаки служат для написания более сжатого кода, как описано следующими утверждениями в упомянутом вами документе:
источник
В основном -> Подстановочные знаки применяют универсальные шаблоны на уровне параметра / аргумента не-универсального метода. Заметка. Это также можно выполнить в genericMethod по умолчанию, но здесь вместо? мы можем использовать сам T.
дженерики пакетов;
У подстановочного знака SO есть свои конкретные варианты использования, подобные этому.
источник