Параметризованный тип Параметры? - PullRequest
4 голосов
/ 22 февраля 2012

Я пытаюсь создать библиотеку с контейнером, который выпускает экземпляры содержащихся в ней объектов в соответствии с дескрипторами, которые ей передаются. Я хотел бы сделать так, чтобы дескриптор определял тип возвращаемого объекта, но дескриптор может указывать ограниченный тип. Как мне это реализовать? Например, самое близкое, что я могу получить:

/*Block 1 - First Attempt.  Compiles, but forces user to cast*/
interface ItemDescriptor<I> {
    Class<? extends I> getType();
}

interface ArchiveContainer<I, D extends ItemDescriptor<? extends I>> {
    Iterable<? extends D> getDescriptors();
    I getItem(D descriptor);
}

//Implementations
class ChannelItemDescriptor<I extends ByteChannel> implements ItemDescriptor<I>
{
    final Class<? extends I>  type;

    ChannelItemDescriptor(Class<I> type) {
        this.type = type;
    }

    @Override Class<? extends I> getType() {return type;}
}

class ChannelArchive implements ArchiveContainer<ByteChannel, ChannelItemDescriptor<? extends ByteChannel>> {
    @Override ByteChannel getItem(ChannelItemDescriptor<? extends ByteChannel> descriptor) {...}
}

Приведенный выше код компилируется, но проблема в том, что ChannelArchive s getItem также может возвращать SeekableByteChannel s. Пользователь этой библиотеки знает это во время компиляции (потому что он знает параметр типа дескриптора), поэтому я стараюсь избегать добавления параметра метода типа Class, чтобы заставить пользователя явно привести возвращенное значение к SeekableByteChannel при необходимости. Я не могу понять, как заставить getItem возвращать определенный подтип ByteChannel, не заставляя пользователя читать. Я хочу сделать это:

/*Block 2 - Test code*/
ChannelArchive archive = ...;
ChannelItemDescriptor<SeekableByteChannel> desc = ...;
ChannelItemDescriptor<ByteChannel> otherDesc = ...;
SeekableByteChannel sbc = archive.getItem(desc);
SeekableByteChannel sbc = archive.getItem(otherDesc); //Should fail to compile, or compile with warning
ByteChannel bc = archive.getItem(otherDesc);

I может добавить параметр Class<? extends I> к каждому методу, но код метода будет полностью игнорировать параметр метода Class! Единственная цель состоит в том, чтобы помочь компилятору выводить типы. Я думаю, что он просто настолько запутывает код, что было бы проще просто заставить пользователя использовать instanceof проверки и приведения.

Я пробовал это:

/*Block 3 - Failed attempt.*/
class ChannelArchive implements ArchiveContainer<ByteChannel, ChannelItemDescriptor<? extends ByteChannel>> {
    //Won't compile, getItem doesn't override
    @Override <II extends ByteChannel> II getItem(ChannelItemDescriptor<II> descriptor) {...}
}

но это не работает: ChannelArchive is not abstract and does not override abstract method getItem(ChannelItemDescriptor<? extends ByteChannel>) in ArchiveContainer. Я предполагаю, что это связано с тем, что параметр второго типа <II extends ByteChannel> имеет тип стирания, отличный от <? extends ByteChannel>?

Я также попробовал это, который компилирует:

/*Block 4 - Almost specific enough*/
interface ArchiveContainer<I, D extends ItemDescriptor<? extends I>> {
    Iterable<? extends D> getDescriptors();
    <II extends I, DD extends ItemDescriptor<II>> II getItem(DD descriptor);
}

class ChannelArchive implements ArchiveContainer<ByteChannel, ChannelItemDescriptor<? extends ByteChannel>> {
    @Override <II extends ByteChannel, DD extends ItemDescriptor<II>> II getItem(DD descriptor) {...}
}

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

Я не понимаю, почему я не могу этого сделать, потому что правильные типы известны во время компиляции. В этом интерфейсе ArchiveContainer мне действительно нужен параметр параметризованного типа, например: <II extends I, DD extends D<II>>. Что я делаю не так?

ПРИМЕЧАНИЕ. На самом деле я не использую ByteChannel и SeekableByteChannel, но то, что я использую, довольно похоже.


Это к ruakh, я остановился на коде в блоке 4. В моем случае крайне маловероятно, что пользователь отправит неправильный подкласс ItemDescriptor при вызове getItem, особенно потому, что возвращаются все дескрипторы от самого ArchiveContainer через getDescriptors!

Ответы [ 2 ]

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

Я думаю, что этот код, который (почти?) Такой же, как ваша третья попытка, так же хорош, как вы собираетесь получить:

// in ArchiveContainer:
<II extends I, DD extends ItemDescriptor<II>> II getItem(DD descriptor);

// in ChannelArchive:
public <II extends ByteChannel, DD extends ItemDescriptor<II>>
    II getItem(DD descriptor)
    { ... }

Обобщения предлагают способ объявить типпеременная с двумя отдельными верхними границами:

public <T extends Foo & Bar> Foo fooBar(T t) { ... }

, но, по-видимому, это недопустимо, когда одна из верхних границ является параметром типа, а не классом или интерфейсом:

Переменные типаимеют необязательную границу, T & I 1 ... I n .Граница состоит из либо переменной типа, или класса или типа интерфейса T , за которыми, возможно, следуют другие типы интерфейса I 1 , ..., I n .[…] Ошибка времени компиляции, если какой-либо из типов I 1 ... I n является типом класса или переменной типа.[ ссылка ]

(подчеркивает мою).Я не знаю, почему это так.

Но я не думаю, что это должно быть большой проблемой.Обратите внимание, что даже после того, как Map был обобщен для Map<K,V>, его метод get все еще принимал тип Object.Естественно, этот метод всегда будет возвращать null, если вы передадите ссылку на объект, который не относится к типу K (так как такой объект никогда не должен был быть вставлен в карту), но это не вредит безопасности типов.

0 голосов
/ 22 февраля 2012

Я знаю, что это, вероятно, не то, что вы хотите услышать, но даже несмотря на то, что дженерики Java выглядят синтаксически как шаблоны C ++, они немного отличаются по своей работе.

Поиск java type erasure в вашей любимой поисковой системе.То, что тип известен во время компиляции, к сожалению, не означает, что этот тип можно восстановить во время выполнения или даже на более поздних этапах компиляции.

...