4

渡された記述子に従って、含まれているオブジェクトのインスタンスを解放するコンテナを使用してライブラリを作成しようとしています。記述子が返されるオブジェクトの型を決定するようにしたいのですが、記述子は境界付きの型を指定できます。これを実装するにはどうすればよいですか?たとえば、私が得ることができる最も近いものは次のとおりです。

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

上記のコードはコンパイルされますが、問題は もChannelArchivesgetItemを返す可能性があること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. これは、2 番目の型パラメーターの<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>>。私は何を間違っていますか?

注: 実際には and は使用しませんがByteChannelSeekableByteChannel使用するものは非常に似ています。


ItemDescriptorブロック 4 のコードに落ち着きました。私の場合、ユーザーがa の呼び出しで間違っgetItemたsublcass を送信する可能性はほとんどありませArchiveContainergetDescriptors

4

2 に答える 2

1

このコードは、3 回目の試行と (ほとんど?) 同じですが、得られるものと同じくらい優れていると思います。

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

ジェネリックは、2 つの個別の上限を持つ型変数を宣言する方法を提供します。

public <T extends Foo & Bar> Foo fooBar(T t) { ... }

しかし、上限の1つがクラスやインターフェースではなく型パラメータである場合、明らかにそれは許可されていません:

型変数には、オプションの境界T & I 1 ... I nがあります。境界は、型変数、またはクラスまたはインターフェイス型Tのいずれかで構成され、場合によってはさらにインターフェイス型I 1、...、 I nが続きます。[…] 型I 1 ... I nのいずれかがクラス型または型変数である場合、コンパイル時エラーになります。【リンク

(私のことを強調します)。これがなぜなのかわかりません。

しかし、これは大きな問題になるべきではないと思います。Mapが にジェネリック化された後でもMap<K,V>、そのgetメソッドはまだ型を取ることに注意してくださいObjectnull型ではないオブジェクトへの参照を渡すと、当然、そのメソッドは常に返されますK(そのようなオブジェクトはマップに挿入されてはならないため) が、これは型の安全性を損なうことはありません。

于 2012-02-22T02:35:35.883 に答える