Я пытаюсь создать библиотеку с контейнером, который выпускает экземпляры содержащихся в ней объектов в соответствии с дескрипторами, которые ей передаются. Я хотел бы сделать так, чтобы дескриптор определял тип возвращаемого объекта, но дескриптор может указывать ограниченный тип. Как мне это реализовать? Например, самое близкое, что я могу получить:
/*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
!