Search code examples
javagenericsnested-generics

Parameterized Type Parameters?


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 SeekableByteChannels 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!


Solution

  • 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.