2014-09-21 2 views
14

При переходе через этот вопрос: «Checking if an object is contained within a linkedlist», я понял, что пользователь пытается передать строковый аргумент к содержит метод в LinkedList типа LinkedList:Список содержит метод не типобезопасен

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

    list.contains("String"); 

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

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

+2

здесь хорошо читать на эту тему http://www.javaworld.com/article/2073330/the-contains-trap-in-java-collections.html – nem035

+0

, связанные, или, вернее, дубликата http://stackoverflow.com/questions/857420/what-are-the-reasons-why-map-getobject-key-is-not-fully-generic и http: // stackoverflow.com/questions/104799/why-arent-java-collections-remove-methods-generic – Marco13

ответ

-1

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

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, вс 21 сентября 00:36:59 MDT 2014]

+0

и ваш источник? !!! –

+1

Это не главное, если вы не укажете общий тип, по умолчанию это будет Object. И фактически каждый Список является гетерогенным, поскольку существует наследование. – kamil09875

8

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

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

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

+0

Вы говорите, что BankAccount идентифицирован AccountId, но ни один из них не расширяет другой. Не следует за ним. Можете ли вы предоставить более подробную информацию, чтобы лучше понять ее? – Gaurav

+0

Да. Помните, чтобы BankAccount.equals принимали AccountId, так как равные должны быть симметричными. Из равноценной документации: она симметрична: для любых непустых опорных значений x и y x.equals (y) должен возвращать true тогда и только тогда, когда y.equals (x) возвращает true. –

+7

Я бы счел это ** очень сомнительным, чтобы реализовать два разных класса таким образом, чтобы один экземпляр одного класса мог быть «равен» экземпляру другого класса. Многие разумные шаблоны использования могут нарушить такую ​​реализацию. (Это относится к классам, которые не являются «равными» с точки зрения общего интерфейса, который они реализуют) – Marco13

1

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

Важное значение, тем не менее, содержит сделки с равенством объектов. Он фактически вызывает метод 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; 

} 
3

Простой ... LinkedList на самом деле объект.

Кроме того,

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

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

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

+0

> Если вы установите метод contains для получения общего, вы отделите его свойства объекта. Таким образом, вы потеряете способность создавать собственные методы hashcode() и equals() <<<< Не могли бы вы объяснить? Я не буду следовать. –

+0

Я согласен с использованием equals и hashCode, но не с вашим заявлением об удалении свойств объекта. Вы все равно можете переопределить метод equals и hashCode в родовом и его подклассах. Фактически, вы можете создать экземпляр подкласса с тем же хэш-кодом, что и общий экземпляр, содержащийся в коллекции. – FranMowinckel

4

Спецификация для содержит метод говорит:

возвращает истину, если и только если эта коллекция содержит, по меньшей мере, один элемент е такой, что (o==null ? e==null : o.equals(e)) [DOCS оракула] [1]

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

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

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

но:

Данная спецификация не должна быть подразумевается, что вызов Collection.contains с аргументом non-null 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)

Смежные вопросы