参数化类型参数?

时间:2012-02-22 01:00:59

标签: java generics nested-generics

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

/*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的{​​{1}}也可以返回getItem。这个库的用户在编译时知道这个(因为他们知道描述符的类型参数),所以我试图避免添加类型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>>ByteChannel,但我使用的内容非常类似。


那就是ruakh,我决定了第4块中的代码。在我的情况下,用户在调用SeekableByteChannel时发送错误的ItemDescriptor sublca的可能性极小,特别是因为描述符全部通过getItemArchiveContainer本身返回!

2 个答案:

答案 0 :(得分:1)

我认为这段代码(差不多?)与你的第三次尝试相同,就像你将得到的一样好:

// 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&amp;我 1 ...我 n 。绑定由 类型变量类或接口类型 T 组成,可能后跟其他接口类型 I 1 ,...,我 n 。 [...]如果任何类型 I 1 ... I n 是类类型或类型变量,则为编译时错误。 [link]

(强调我的)。我不知道为什么会这样。

但我不认为这应该是个大问题。请注意,即使Map被通用化为Map<K,V>,其get方法仍然采用类型Object。当然,如果您传入对不属于null类型的对象的引用(因为这样的对象永远不应该插入到地图中),该方法将始终返回K,但这不会造成伤害类型安全。

答案 1 :(得分:0)

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

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