Почему коллекции Java не удаляют методы общего характера? - PullRequest
137 голосов
/ 19 сентября 2008

Почему Collection.remove (Object o) не является универсальным?

Похоже, что Collection<E> может иметь boolean remove(E o);

Затем, когда вы случайно попытаетесь удалить (например) Set<String> вместо каждой отдельной строки из Collection<String>, это будет ошибкой времени компиляции вместо проблемы отладки позже.

Ответы [ 9 ]

72 голосов
/ 02 мая 2009

remove()Map, а также в Collection) не является универсальным, потому что вы должны иметь возможность передавать любой тип объекта в remove(). Удаленный объект не обязательно должен быть того же типа, что и объект, который вы передаете в remove(); это только требует, чтобы они были равны. Из спецификации remove(), remove(o) удаляет объект e, так что (o==null ? e==null : o.equals(e)) равен true. Обратите внимание, что нет ничего требующего, чтобы o и e были одного типа. Это следует из того факта, что метод equals() принимает в качестве параметра Object, а не только тот же тип, что и объект.

Хотя, как правило, верно, что многие классы имеют equals(), определенные так, что его объекты могут быть равны только объектам своего собственного класса, что, конечно, не всегда так. Например, спецификация для List.equals() гласит, что два объекта List равны, если они оба являются списками и имеют одинаковое содержимое, даже если они являются различными реализациями List. Итак, возвращаясь к примеру в этом вопросе, можно иметь Map<ArrayList, Something> и для меня вызвать remove() с LinkedList в качестве аргумента, и он должен удалить ключ, представляющий собой список с таким же содержимым. Это было бы невозможно, если бы remove() были универсальными и ограничивали тип аргумента.

68 голосов
/ 20 сентября 2008

Джош Блох и Билл Пью ссылаются на эту проблему в Java Puzzlers IV: The Призрачная справочная угроза, атака клона и месть Сдвиг .

Джош Блох говорит (6:41), что они пытались обобщить метод get Map, метод remove и некоторые другие, но «это просто не сработало».

Слишком много разумных программ, которые нельзя было бы обобщить, если вы разрешаете только универсальный тип коллекции как тип параметра. Приведенный им пример представляет собой пересечение List из Number s и List из Long с.

11 голосов
/ 19 сентября 2008

Поскольку, если ваш параметр типа является групповым символом, вы не можете использовать универсальный метод удаления.

Кажется, я вспоминаю, что сталкивался с этим вопросом с помощью метода get (Object) Map. Метод get в этом случае не является универсальным, хотя разумно ожидать, что ему будет передан объект того же типа, что и параметр первого типа. Я понял, что если вы передаете вокруг Карт с подстановочным знаком в качестве параметра первого типа, то нет способа извлечь элемент из Карты с помощью этого метода, если этот аргумент был универсальным. Аргументы подстановочных знаков не могут быть действительно удовлетворены, потому что компилятор не может гарантировать, что тип является правильным. Я предполагаю, что причина добавления является общей, что вы должны гарантировать, что тип правильный, прежде чем добавлять его в коллекцию. Однако, при удалении объекта, если тип неправильный, он все равно ничего не будет соответствовать. Если бы аргумент был подстановочным знаком, метод был бы просто непригодным для использования, даже если у вас может быть объект, который вы можете ГАРАНТИРОВАТЬ, принадлежит к этой коллекции, поскольку вы только что получили ссылку на него в предыдущей строке ....

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

6 голосов
/ 27 января 2009

Помимо других ответов, есть и другая причина, по которой метод должен принимать Object, то есть предикаты. Рассмотрим следующий пример:

class Person {
    public String name;
    // override equals()
}
class Employee extends Person {
    public String company;
    // override equals()
}
class Developer extends Employee {
    public int yearsOfExperience;
    // override equals()
}

class Test {
    public static void main(String[] args) {
        Collection<? extends Person> people = new ArrayList<Employee>();
        // ...

        // to remove the first employee with a specific name:
        people.remove(new Person(someName1));

        // to remove the first developer that matches some criteria:
        people.remove(new Developer(someName2, someCompany, 10));

        // to remove the first employee who is either
        // a developer or an employee of someCompany:
        people.remove(new Object() {
            public boolean equals(Object employee) {
                return employee instanceof Developer
                    || ((Employee) employee).company.equals(someCompany);
        }});
    }
}

Дело в том, что объект, передаваемый методу remove, отвечает за определение метода equals. Построение предикатов становится очень простым.

5 голосов
/ 29 мая 2012

Предположим, что у каждого есть коллекция Cat и некоторые ссылки на объекты типов Animal, Cat, SiameseCat и Dog. Спрашивать коллекцию, содержит ли она объект, на который ссылается ссылка Cat или SiameseCat, кажется разумным. Спросить, содержит ли он объект, на который ссылается ссылка Animal, может показаться странным, но все же это вполне разумно. В конце концов, рассматриваемый объект может быть Cat и может появляться в коллекции.

Далее, даже если объект оказывается чем-то отличным от Cat, нет проблем с тем, чтобы сказать, появляется ли он в коллекции - просто ответьте «нет, это не так». Коллекция «lookup-style» некоторого типа должна быть способна осмысленно принимать ссылку любого супертипа и определять, существует ли объект в коллекции. Если переданная ссылка на объект имеет несвязанный тип, коллекция не может содержать его, поэтому запрос в некотором смысле не имеет смысла (он всегда будет отвечать «нет»). Тем не менее, поскольку нет никакого способа ограничить параметры подтипами или супертипами, наиболее практично просто принять любой тип и ответить «нет» для любых объектов, тип которых не связан с типом коллекции.

3 голосов
/ 20 сентября 2008

Я всегда думал, что это потому, что у remove () нет причин заботиться о том, какой тип объекта вы ему предоставите. Независимо от этого достаточно легко проверить, является ли этот объект одним из объектов, содержащихся в Collection, поскольку он может вызывать equals () для всего. Необходимо проверить тип в add (), чтобы убедиться, что он содержит только объекты этого типа.

0 голосов
/ 26 ноября 2015

Другая причина - из-за интерфейсов. Вот пример, чтобы показать это:

public interface A {}

public interface B {}

public class MyClass implements A, B {}

public static void main(String[] args) {
   Collection<A> collection = new ArrayList<>();
   MyClass item = new MyClass();
   collection.add(item);  // works fine
   B b = item; // valid
   collection.remove(b); /* It works because the remove method accepts an Object. If it was generic, this would not work */
}
0 голосов
/ 19 сентября 2008

Потому что это сломало бы существующий (до Java5) код. например.,

Set stringSet = new HashSet();
// do some stuff...
Object o = "foobar";
stringSet.remove(o);

Теперь вы можете сказать, что приведенный выше код неверен, но предположим, что o произошел из разнородного набора объектов (то есть он содержал строки, числа, объекты и т. Д.). Вы хотите удалить все совпадения, что было допустимо, потому что при удалении игнорировались бы не строки, потому что они были неравны. Но если вы удалите его (String o), это больше не работает.

0 голосов
/ 19 сентября 2008

Удалить не является универсальным методом, поэтому существующий код, использующий неуниверсальную коллекцию, будет по-прежнему компилироваться и работать так же.

Подробнее см. http://www.ibm.com/developerworks/java/library/j-jtp01255.html.

Редактировать: комментатор спрашивает, почему метод add является универсальным. [... удалил мое объяснение ...] Второй комментатор ответил на вопрос от firebird84 гораздо лучше меня.

...