Список содержит метод не типа безопасного

Просматривая этот вопрос: "Проверка наличия объекта в связанном списке", я понял, что пользователь пытается передать аргумент String методу contains LinkedList типа LinkedList:

    LinkedList<LinkedList> list = new LinkedList<LinkedList>();

    list.contains("String");

Это не вызывает никакой ошибки компиляции, потому что метод 'contains' принимает java.lang.Object и передает ему значение строки.

Итак, просто из любопытства, я хотел понять, почему этот метод был выбран для принятия "Объекта", когда он мог быть ограничен, чтобы принимать только тип списка (точно так же, как добавить). Разве это не побеждает всю цель Generics, то есть "более сильные проверки типа во время компиляции"?

Ответы

Ответ 1

Скажем, у вас есть BankAccount, идентифицированный AccountId (оба являются классами, не расширяя другого).

Теперь скажем, что у вас есть LinkedList<BankAccount> accounts; и вы хотите увидеть, есть ли учетная запись с данным ID.

Теперь AccountId.equals() может принять BankAccount и сравнить себя с идентификатором учетной записи. При этом вы сможете вызвать accounts.contains(account_id), и он будет работать, даже если аргумент contains() имеет тип, полностью не связанный с типом коллекции.

Ответ 2

В спецификации метода contains указано:

возвращает true тогда и только тогда, когда эта коллекция содержит хотя бы один элемент e такой, что (o==null ? e==null : o.equals(e))[docs oracle] [1]

Однако он также говорит, что он мог бы выбросить NullPointerException, если коллекция не разрешает нулевые элементы или ClassCastException, если тип указанного элемента несовместим с этой коллекцией. Они отмечены как optional.

Кроме того, он также говорит, что:

Многие методы в интерфейсах Framework Collections определены в терминах метода equals

а

Эта спецификация не должна толковаться как подразумевающая, что вызов Collection.contains с непустым аргументом o приведет к вызову o.equals(e) для любого элемента e

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

Я объясню последний пример:

public class A {

   public void initialize() {
      // Lots of code and heavy initialization
   }

   public String id;

   @Override
   public int hashCode() {
     return id.hashCode();
   }

   @Override
   public boolean equals(Object o) {
      return this.hashCode() == o.hashCode();
   }
}

И затем:

SomeCollection<A> collection = new SomeCollection<A>(); 

// Create an element and add it to the collection
A a = new A();
a.initialize(); // Heavy initialization
element.id = "abc";
collection.add(a);

// Check if the collection contains that element

// We create a second object with the same id, but we do not initialize it
A b = new A();
b.id = "abc";

// This works for many common collections (i.e. ArrayList and HashSet)
collection.contains(b);

На самом деле, существует больше методов, таких как indexOf(Object o) и remove(Object o), которые следуют этому примеру. Поэтому я не думаю, что это совместимо, но оно специально предназначено для таких решений.

http://docs.oracle.com/javase/7/docs/api/java/util/Collection.html#contains(java.lang.Object)

Ответ 3

Простой... LinkedList - это фактически объект.

Кроме того,

Скажите, что у вас есть карта для каждого из ваших связанных списков:

  • CustomMapItem 1: LinkedList 1
  • CustomMapItem 2: LinkedList 2
  • CustomMapItem 3: LinkedList 3

Поскольку contains() принимает объект как параметр, вы можете установить методы equals() и hashcode() для CustomMapItem, чтобы вы могли найти соответствующий LinkedList.

Ответ 4

Прежде всего метод contains не изменяет базовую коллекцию, поэтому у нее нет строгого требования, такого как метод add.

Более важно, однако, содержит сделки с равенством объектов. Он фактически вызывает метод equals для элементов списка. Может показаться, что для того, чтобы два объекта были равными, они должны быть, по крайней мере, одного типа, но на самом деле это не так. Это полностью зависит от того, насколько хорошо определяется ваш метод equals.

Пример:

List<A> myList = new LinkedList<A>();
...
B b = new B();
myList.contains(b); //expected true if 'value' attribute is equal.


class A {
     int value;
     public boolean equals(Object object){
        if(object instanceof A && ((A)object).getValue() == this.value)
        return true;
        else if(object instanceof B && ((B)object).getValue() == this.value)
        return true;
        else
        return false;

}

И аналогично

 class B {
     int value;
     public boolean equals(Object object){
        if(object instanceof A && ((A)object).getValue() == this.value)
        return true;
        else if(object instanceof B && ((B)object).getValue() == this.value)
        return true;
        else
        return false;

}

Ответ 5

Просто для обратной совместимости. Хотя дженерики полезны для ограничения содержимого коллекции однородным типом, все же остается допустимым вариантом использования, чтобы коллекция содержала гетерогенные объекты, если вы этого захотите.

public static void main(String[] args)
{
    List<String> homogeneousStringList = new ArrayList<String>();

    homogeneousStringList.add("Foo");
    homogeneousStringList.add("Bar");

    List heterogeneousObjectList = new ArrayList();

    heterogeneousObjectList.add("Foo");
    heterogeneousObjectList.add(Integer.valueOf(1));
    heterogeneousObjectList.add(new Date());

    System.out.println(homogeneousStringList.toString());
    System.out.println(heterogeneousObjectList.toString());
}

Производит следующий вывод:

[Foo, Bar]

[Foo, 1, Sun Sep 21 00:36:59 MDT 2014]