参数化类型参数



我正试图用一个容器创建库,该容器根据传递的描述符释放所包含对象的实例。我想让描述符确定返回对象的类型,但描述符可以指定有界类型。我该如何实现?例如,我能得到的最接近的是:

/*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) {...}
}

上面的代码编译了,但问题是ChannelArchivegetItem也可以返回SeekableByteChannel。这个库的用户在编译时就知道这一点(因为他们知道描述符的类型参数),所以我尽量避免添加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);

可以为每个方法添加一个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>>。我做错了什么?

注意:我实际上并没有使用ByteChannelSeekableByteChannel,但我使用的是非常相似的。


这是给鲁阿赫的,我已经确定了第4块的代码。在我的情况下,用户极不可能在对getItem的调用中发送错误的ItemDescriptor子类,尤其是因为描述符都是通过getDescriptorsArchiveContainer本身返回的!

我认为这段代码(几乎?)与您的第三次尝试一样好:

// 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&I1。。。In。绑定由类型变量类或接口类型T组成,后面可能是其他接口类型i1。。。,In。[…]如果任何类型i1。。。In是一个类类型或类型变量。[链接]

(重点是我的)。我不知道为什么。

但我认为这不应该是个大问题。注意,即使在Map被泛型为Map<K,V>之后,其get方法仍然采用类型Object。当然,如果传入对非K类型的对象的引用,该方法将始终返回null(因为这样的对象不应该插入到映射中),但这不会损害类型安全。

我知道这可能不是你想听到的,但即使Java泛型在语法上看起来像C++模板,它们的工作方式也有很大不同。

在您喜爱的搜索引擎中查找java type erasure。不幸的是,仅仅因为一个类型在编译时是已知的,并不意味着该类型在运行时甚至在以后的编译阶段是可恢复的。

最新更新