I'm trying to create library with a container that releases instances of its contained objects according to descriptors it is passed. I'd like to make it so the descriptor determines the type of the returned object, but the descriptor can specify a bounded type. How do I implement this? For example the closest I can get is:
/*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) {...}
}
The above code compiles, but the problem is ChannelArchive
's getItem
can return SeekableByteChannel
s as well. The user of this library knows this at compile time (because they know the type parameter of the descriptor), so I'm trying to avoid adding a method parameter of type Class
for forcing the user to explicitly cast the returned value to SeekableByteChannel
when necessary. I can't figure out how to get getItem
to return a specific subtype of ByteChannel
without forcing the user to cast. I want to do this:
/*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 could add a Class<? extends I>
parameter to each method, but the code for the method would completely ignore Class
method parameter! It's only purpose would be to help the compiler infer types. I think it just obfuscates the code so much that it would be easier to just have the user use instanceof
checks and casts.
I've tried this:
/*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) {...}
}
but that doesn't work: ChannelArchive is not abstract and does not override abstract method getItem(ChannelItemDescriptor<? extends ByteChannel>) in ArchiveContainer
. I assume this is because the second type parameter <II extends ByteChannel>
has different type erasure than <? extends ByteChannel>
?
I've also tried this, which compiles:
/*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) {...}
}
Even though it compiles, it won't really work because I need a ChannelItemDescriptor
inside that method, and the resulting cast would defeat the purpose of using the added type-safety of generics.
I don't see why I can't do it, because the right types are known at compile time. What I really need on that ArchiveContainer
interface is a parameterized type parameter, like: <II extends I, DD extends D<II>>
. What am I doing wrong?
NOTE: I don't actually use ByteChannel
and SeekableByteChannel
, but what I do use is quite similiar.
That's to ruakh, I settled on the code in block 4. In my case, its highly unlikely the user would send the wrong sublcass of ItemDescriptor
in a call to a getItem
, especially because the descriptors are all returned from the ArchiveContainer
itself via getDescriptors
!
I think this code, which is (almost?) the same as your third attempt, is as good as you're going to get:
// 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)
{ ... }
Generics do offer a way to declare a type variable with two separate upper bounds:
public <T extends Foo & Bar> Foo fooBar(T t) { ... }
but apparently that's not allowed when one of the upper bounds is a type-parameter rather than a class or interface:
Type variables have an optional bound, T & I1 ... In. The bound consists of either a type variable, or a class or interface type T possibly followed by further interface types I1 , ..., In. […] It is a compile-time error if any of the types I1 ... In is a class type or type variable. [link]
(emphases mine). I don't know why this is.
But I don't think this should be a big problem. Note that, even after Map
was genericized to Map<K,V>
, its get
method still took type Object
. Naturally that method will always return null
if you pass in a reference to an object that's not of type K
(since such an object should never have been inserted into the map), but this doesn't harm type-safety.