Почему Java Collection не удаляет общие методы?

Почему не Collection.remove(Object o) generic?

Кажется, что Collection<E> может иметь boolean remove(E o);

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

Ответ 1

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

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

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

Ответ 2

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

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

Ответ 3

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

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

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

Ответ 4

В дополнение к другим ответам есть еще одна причина, по которой метод должен принимать 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

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

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

Ответ 6

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

Ответ 7

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

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

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

Ответ 8

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

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 */
}

Ответ 9

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

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

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