Почему Java Collections не являются универсальными методами удаления?

121

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

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

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

  • 12
    Это может действительно укусить вас, когда вы сочетаете это с автобоксом. Если вы пытаетесь удалить что-то из списка и передаете ему целое число вместо int, то вызывается метод remove (Object).
  • 2
    Подобный вопрос относительно карты: stackoverflow.com/questions/857420/…
Теги:
generics
collections

9 ответов

59
Лучший ответ

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

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

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

  • 6
    Почему add () может принимать типизированный параметр, а remove () не может, все еще немного за пределами моего понимания. Джош Блох был бы окончательным справочником для вопросов Коллекции. Это может быть все, что я получаю, не пытаясь создать аналогичную реализацию коллекции и убедиться в этом сам. :( Спасибо.
  • 2
    Крис - прочитайте учебник по Java Generics Tutorial, он объяснит почему.
Показать ещё 5 комментариев
64

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() был общим и ограничивал его тип аргумента.

  • 1
    Но если бы вы определили карту как Map <List, Something> (вместо ArrayList), ее можно было бы удалить с помощью LinkedList. Я думаю, что этот ответ неверен.
  • 1
    @kilaka, но если вы это сделаете, то вы можете добавить не ArrayList на карту, что может быть нежелательно.
Показать ещё 5 комментариев
11

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

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

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

  • 1
    Не могли бы вы подробнее остановиться на этом?
  • 2
    Так что, возможно, это хорошо - подстановочный знак делает коллекцию неизменной !!!
6

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

  • 0
    Инвестор? (Наполнитель наполнитель наполнитель)
  • 0
    Последнее не очень хорошая идея: как реализуется list.remove () - developer.equals (yourAnonymousObject) или yourAnonymousObject.equals (разработчик)?
Показать ещё 13 комментариев
5

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

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

  • 1
    «Если переданная ссылка на объект имеет несвязанный тип, коллекция не сможет его содержать». Неправильно. Это должно только содержать что-то равное этому; и объекты разных классов могут быть равны.
  • 0
    "может показаться хитрым, но все же вполне разумно" Рассмотрим мир, в котором объект одного типа не всегда можно проверить на равенство с объектом другого типа, потому что параметризован тип, которому он может быть равен (аналогично параметризации Comparable для типов, с которыми вы можете сравнивать). Тогда было бы неразумно позволять людям передавать что-то не связанное с этим типом.
Показать ещё 2 комментария
3

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

-1

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

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

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

  • 2
    Тогда почему метод add является общим?
  • 0
    @ firebird84 remove (Object) игнорирует объекты неправильного типа, но remove (E) может вызвать ошибку компиляции. Это изменило бы поведение.
Показать ещё 1 комментарий
-3

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

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
    Вы показываете что-то, что вы можете избежать, потому что remove() не является ковариантным. Вопрос, однако, заключается в том, должно ли это быть разрешено. ArrayList#remove() работает посредством равенства значений, а не ссылки на равенство. Почему вы ожидаете, что B будет равен A ? В вашем примере это может быть, но это странное ожидание. Я бы предпочел, чтобы вы предоставили здесь аргумент MyClass .
-4

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

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

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

  • 4
    Если бы я создал экземпляр List <String>, я бы ожидал, что смогу вызвать только List.remove (someString); Если мне нужно поддерживать обратную совместимость, я бы использовал необработанный List - List <?>, Тогда я могу вызвать list.remove (someObject), нет?
  • 5
    Если вы замените «удалить» на «добавить», то этот код будет нарушен тем, что фактически было сделано в Java 5.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню