Как правильно реализовать Map для пользовательских типов, используя дженерики во вложенных классах - PullRequest
0 голосов
/ 22 февраля 2011

Короче говоря, я пытаюсь написать реализацию AbstractMap, и я столкнулся с проблемой синтаксиса, которую я не понимаю.

Рассмотрим следующие два эскиза (идентификаторы с My перед ними являются пользовательскими типами).


(1) Не работает:

public class CustomMap extends AbstractMap<MyKey,MyValue> {
    private Set<Map.Entry<MyKey,MyValue>> data = null ;
    public static class MyMapEntry<K,V> implements Map.Entry<K,V> { 
        //...
    }
    public static class MyEntrySet<E> extends AbstractSet<E> {
        //...
    }
    public static class MyEntrySetIterator<E> implements Iterator<E> {
        //...
    }
    { // initializer block
        this.data = new MyEntrySet<MyMapEntry<MyKey,MyValue>>() ;
    }
    // (constructors and methods of the Map interface)

}

(2) Рабочая:

public class CustomMap extends AbstractMap<MyKey,MyValue> {
    private Set<Map.Entry<MyKey,MyValue>> data = null ;
    public static class MyMapEntry<K,V> implements Map.Entry<K,V> { 
        //...
    }
    public static class MyEntrySet<E> extends AbstractSet<E> {
        //...
    }
    public static class MyEntrySetIterator<E> implements Iterator<E> {
        //...
    }
    { // initializer block
        this.data = new MyEntrySet<Map.Entry<MyKey,MyValue>>() ;
    }
    // (constructors and methods of the Map interface)

}

Единственное отличие состоит в том, как я инстанцировал this.data (Set, поддерживающий Map): очевидно, компилятор не принимает экземпляр Map.Entry (например, MyMapEntry) в качестве параметризованного типа MyEntrySet.

Компилятор жалуется на "несовместимые типы".

Интересно, почему.

Пожалуйста, оставляйте комментарии, если мне нужно уточнить, ТИА,

FK82

Ответы [ 4 ]

4 голосов
/ 22 февраля 2011

Вот почему:

MyEntrySet<MyMapEntry<MyKey,MyValue>>() mySet = 
   new MyEntrySet<MyMapEntry<MyKey,MyValue>>();

this.data = mySet; // Imagine that this cast is possible
this.data.add(new SomeOtherMapEntry());

MyMapEntry<MyKey, MyValue> e = 
    mySet.iterator().next(); // Unexpected ClassCastException!

MyEntrySet<MyMapEntry<MyKey,MyValue>> - это Set, который может содержать только экземпляры MyMapEntry<MyKey,MyValue>.Set<Map.Entry<MyKey,MyValue>> может содержать произвольные экземпляры Map.Entry<MyKey,MyValue>.Применение между ними нарушит безопасность типов.

2 голосов
/ 22 февраля 2011

Это распространенная ошибка в дженериках, концептуально сложно приспособиться к тому, что List<Child> нельзя присвоить List<Parent>, где Child является подтипом Parent.

1 голос
/ 22 февраля 2011

Это не твоя вина. В API карты

    Set<Map.Entry<K,V>> entrySet() 

Более сложная версия должна быть

    Set<? extends Map.Entry<K,V>> entrySet() 

Чтобы MyEntrySet<MyMapEntry> мог работать просто отлично.


Но мне нравится 1-я версия. Мне так надоели эти дурацкие символы везде, это заставляет меня рвать.

Был гораздо лучший синтаксис, используемый всеми исследовательскими работами, на которых основывался Java Generics

    Set<+Map.Entry<K,V>> entrySet() 

Некоторые умники в Sun не верят, что Java-программисты могут справиться с этим, поэтому у нас теперь есть гораздо более беспорядочный, безумный синтаксис.

1 голос
/ 22 февраля 2011

Я расскажу о других ответах на более простом примере.

Рассмотрим

List<String> strings = new ArrayList<String>();
List<Object> objects = strings; // this is what you are doing in your constructor
objects.add("some string");

Объекты объявили, что он будет содержать только Объекты, а строки объявили, что он будет толькосодержат строкиТак что, безусловно, назначение строк объектам должно быть хорошо, так как все строки являются объектами?Нам даже разрешено добавлять строки к объектам без проблем.

Однако как насчет этой ситуации.

objects.add(new Object());

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

...