Почему Collection.remove (Object o) не является универсальным?
Похоже, Collection<E>
мог иметьboolean remove(E o);
Затем, когда вы случайно попытаетесь удалить (например) Set<String>
вместо каждой отдельной строки из a Collection<String>
, это будет ошибкой времени компиляции, а не проблемой отладки позже.
java
api
generics
collections
Крис Маззола
источник
источник
Ответы:
Джош Блох и Билл Пью ссылаются на эту проблему в Java Puzzlers IV: Справочная угроза фантома, Атака клона и Месть сдвига .
Джош Блох говорит (6:41), что они пытались обобщить метод get из Map, метод remove и некоторые другие, но «это просто не сработало».
Существует слишком много разумных программ, которые нельзя было бы генерировать, если вы разрешаете только универсальный тип коллекции в качестве типа параметра. Приведенный им пример является пересечением a
List
ofNumber
s и aList
ofLong
s.источник
remove()
(Map
как и вCollection
) не является универсальным, потому что вы должны иметь возможность передавать объект любого типаremove()
. Удаленный объект не обязательно должен быть того же типа, что и объект, которому вы передаетеremove()
; это только требует, чтобы они были равны. Из спецификацииremove()
,remove(o)
удаляет объектe
таким, какой(o==null ? e==null : o.equals(e))
естьtrue
. Обратите внимание, что нет ничего требующегоo
иe
быть того же типа. Это следует из того факта, чтоequals()
метод принимает в качествеObject
параметра, а не просто тот же тип, что и объект.Хотя обычно может быть верно, что многие классы
equals()
определили так, что его объекты могут быть равны только объектам своего собственного класса, что, конечно, не всегда так. Например, спецификация forList.equals()
говорит, что два объекта List равны, если они оба являются списками и имеют одинаковое содержимое, даже если они являются разными реализациямиList
. Итак, возвращаясь к примеру в этом вопросе, можно иметьMap<ArrayList, Something>
и для меня вызовremove()
сLinkedList
аргументом as, и он должен удалить ключ, представляющий собой список с тем же содержимым. Это было бы невозможно, если бы ониremove()
были общими и ограничивали тип аргумента.источник
equals()
метод? Я мог видеть больше преимуществ для безопасности типов вместо этого «либертарианского» подхода. Я думаю, что большинство случаев с текущей реализацией связано с ошибками, попадающими в наш код, а не с радостью, которую обеспечивает гибкостьremove()
метода.equals()
метода»?T
должен быть объявлен как параметр типа в классе иObject
не имеет параметров типа. Не существует способа иметь тип, который ссылается на «декларирующий класс».Equality<T>
сequals(T other)
. Тогда вы могли бы иметьremove(Equality<T> o)
иo
просто какой-то объект, который можно сравнить с другимT
.Потому что, если ваш параметр типа является подстановочным знаком, вы не можете использовать универсальный метод удаления.
Кажется, я вспоминаю, что столкнулся с этим вопросом с помощью метода get (Object) Map. Метод get в этом случае не является универсальным, хотя следует ожидать, что ему будет передан объект того же типа, что и параметр первого типа. Я понял, что если вы передаете вокруг Карт с подстановочным знаком в качестве параметра первого типа, то нет способа извлечь элемент из Карты с помощью этого метода, если этот аргумент был универсальным. Аргументы подстановочных знаков не могут быть действительно удовлетворены, потому что компилятор не может гарантировать, что тип является правильным. Я предполагаю, что причина добавления является общей, что вы должны гарантировать, что тип правильный, прежде чем добавлять его в коллекцию. Однако, при удалении объекта, если тип неправильный, он все равно ничего не будет соответствовать.
Я, наверное, не очень хорошо это объяснил, но мне это кажется достаточно логичным.
источник
В дополнение к другим ответам есть еще одна причина, по которой метод должен принимать
Object
предикаты. Рассмотрим следующий пример:Дело в том, что объект, передаваемый
remove
методу, отвечает за определениеequals
метода. Построение предикатов становится очень простым.источник
yourObject.equals(developer)
, как описано вequals
метода, а именно симметрию. Метод remove связан только со своей спецификацией, пока ваши объекты удовлетворяют спецификации equals / hashCode, поэтому любая реализация может выполнить сравнение наоборот. Кроме того, ваш объект-предикат не реализует.hashCode()
метод (не может реализовать последовательно равные), поэтому вызов remove никогда не будет работать с коллекцией на основе Hash (например, HashSet или HashMap.keys ()). То, что он работает с ArrayList - чистая удача.Collection.remove
, не нарушая свой контракт (если порядок соответствует равным). И разнообразный ArrayList (или AbstractCollection, я думаю) с перевернутым вызовом equals все равно будет правильно реализовывать контракт - это ваша вина, если он не будет работать так, как задумано, поскольку вы нарушаетеequals
контракт.Предположим , один имеет коллекцию
Cat
, а некоторые ссылки на объекты типовAnimal
,Cat
,SiameseCat
иDog
. Спрашивать коллекцию, содержит ли она объект, на который указывает ссылкаCat
или,SiameseCat
кажется разумным. Спросить, содержит ли он объект, на которыйAnimal
ссылается ссылка, может показаться странным, но это все же вполне разумно. В конце концов, рассматриваемый объект может бытьCat
и может появиться в коллекции.Кроме того, даже если объект оказывается чем-то отличным от a
Cat
, нет проблем с тем, чтобы сказать, появляется ли он в коллекции - просто ответьте «нет, это не так». Коллекция «lookup-style» некоторого типа должна быть способна осмысленно принимать ссылку любого супертипа и определять, существует ли объект в коллекции. Если переданная ссылка на объект имеет несвязанный тип, коллекция не сможет его содержать, поэтому запрос в некотором смысле не имеет смысла (он всегда будет отвечать «нет»). Тем не менее, поскольку нет никакого способа ограничить параметры подтипами или супертипами, наиболее практично просто принять любой тип и ответить «нет» для любых объектов, тип которых не связан с типом коллекции.источник
Comparable
параметризации для типов, с которыми можно сравнивать). Тогда было бы неразумно позволять людям передавать что-то не связанное с этим типом.A
иB
одного типа, а такжеX
иY
другого, так чтоA
>B
иX
>Y
. ЛибоA
>Y
иY
<A
, либоX
>B
иB
<X
. Эти отношения могут существовать только в том случае, если для сравнения величин известны оба типа. В отличие от этого, метод сравнения равенства объектов может просто объявить себя неравным по отношению к чему-либо из любого другого типа, без необходимости что-либо знать о другом рассматриваемом типе. Объект типаCat
может не иметь представления, является ли он ...FordMustang
, но ему не составит труда сказать, равен ли он такому объекту (ответ, очевидно, будет «нет»).Я всегда думал, что это потому, что у remove () нет причин заботиться о том, какой тип объекта вы ему предоставите. Независимо от этого достаточно легко проверить, является ли этот объект одним из объектов, содержащихся в Collection, поскольку он может вызывать equals () для чего угодно. Необходимо проверить тип в add (), чтобы убедиться, что он содержит только объекты этого типа.
источник
Это был компромисс. Оба подхода имеют свои преимущества:
remove(Object o)
remove(E e)
обеспечивает большую безопасность типов для того, что большинство программ хотят делать, обнаруживая незаметные ошибки во время компиляции, например, ошибочно пытаясь удалить целое число из списка шортов.Обратная совместимость всегда была главной целью при разработке Java API, поэтому было выбрано remove (Object o), потому что это облегчало генерирование существующего кода. Если бы обратная совместимость не была проблемой, я предполагаю, что дизайнеры выбрали бы remove (E e).
источник
Remove не является универсальным методом, поэтому существующий код, использующий неуниверсальную коллекцию, будет по-прежнему компилироваться и иметь то же поведение.
См. Http://www.ibm.com/developerworks/java/library/j-jtp01255.html для получения подробной информации.
Редактировать: комментатор спрашивает, почему метод add является универсальным. [... удалил мое объяснение ...] Второй комментатор ответил на вопрос от firebird84 намного лучше меня.
источник
Другая причина - из-за интерфейсов. Вот пример, чтобы показать это:
источник
remove()
не является ковариантным. Вопрос, однако, заключается в том, должно ли это быть разрешено.ArrayList#remove()
работает путем равенства значений, а не ссылочного равенства. Почему вы ожидаете, что aB
будет равноA
? В вашем примере это может быть, но это странное ожидание. Я бы предпочел, чтобы выMyClass
привели здесь аргумент.Потому что это сломало бы существующий (до Java5) код. например,
Теперь вы можете сказать, что приведенный выше код неверен, но предположим, что o произошел от разнородного набора объектов (т. Е. Он содержал строки, числа, объекты и т. Д.). Вы хотите удалить все совпадения, что было допустимо, потому что при удалении игнорировались бы не строки, потому что они были неравны. Но если вы удалите его (String o), это больше не работает.
источник