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

StackOverflow https://stackoverflow.com/questions/104799

Вопрос

Почему нет Collection.удалить (Объект o) универсальный?

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

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

Это было полезно?

Решение

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

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

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

Другие советы

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.Итак, возвращаясь к примеру в этом вопросе, можно иметь Map<ArrayList, Something> и чтобы я позвонил remove() с помощью LinkedList в качестве аргумента, и он должен удалить ключ, который представляет собой список с тем же содержимым.Это было бы невозможно, если бы remove() были универсальными и ограничивали тип его аргумента.

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

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

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

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

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

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

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

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

Видишь http://www.ibm.com/developerworks/java/library/j-jtp01255.html за подробностями.

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top