3

JavaGenericsについて質問があります。以下のコードでは、インターフェースAを実装する必要がある別のタイプによってパラメーター化されたインターフェースBがあります。

このコードは正しいです。質問は:なぜそれが次のlist()メソッド宣言で機能しないのですか?

private <X extends A, Y extends B<X>> List<Y> list()

作業コード:

public interface A {
}
public interface B<T extends A> {
}
public class Test {

    private static class AA implements A {}
    private static class BB implements B<AA> {}

    private <R extends A, X extends R, Y extends B<X>> List<Y> list() {
        return null;
    }

    private void test() {
        List<BB> l = list();
    }
}

編集:私はコードを作り直しました。今、私たちは鳥が作ることができる音によって鳥をパレメトリ化しています。質問は、なぜuseless_tが必要なのですか?

public class Test {
    public interface Sound {
    }
    public interface Bird<T extends Sound> {
    }

    private static class Quack implements Sound {}
    private static class Duck implements Bird<Quack> {}


    private <useless_t extends Sound, sound_t extends useless_t, bird_t extends Bird<sound_t>> List<bird_t> list() {
            return null;
    }

    private void test() {
            List<Duck> l = list();
    }
}
4

3 に答える 3

3

私のEclipseIDEは、コード例をそのままコンパイルしません。ただし、追加のタイプヒントが与えられると、コンパイルされます。2番目の例では、typeパラメーターの有無にかかわらず、useless_t次の行はコンパイルされません。

List<Duck> l = list();

しかし、以下は私のためにコンパイルします:

List<Duck> l = this.<Sound, Quack, Duck> list();

因数分解すると、useless_t以下もコンパイルされます。

List<Duck> l = this.<Quack, Duck> list();

したがって、基本的にはコンパイラが型パラメータを正しく取得しない問題であり、型を明示的に指定する必要があります。

更新:追加によって違いが生じたプログラムに実際に遭遇したuseless_t場合は、安全でない地形にいて、指定されていないコンパイラの動作に依存しています。

異なるコンパイラの動作が異なる問題、つまり型推論に遭遇しました。JLSは、コンパイラーが型を推測しなければならない場所と、推測を拒否しなければならない場所について完全に明確ではないため、ここには小刻みに動く余地があります。Eclipseコンパイラのバージョンとjavacのバージョンが異なると、タイプを推測する場所が異なります。javacの場合、これは異なる1.5.0_xバージョンを比較する場合でも当てはまり、Eclipseコンパイラーは通常javacよりも多くを推測できます。

すべての一般的なコンパイラが成功する場合にのみ型推論に依存する必要があります。それ以外の場合は、型のヒントを提供します。一時変数を導入するのと同じくらい簡単な場合もありますが、(例のように)var.<Types>method()構文を使用する必要がある場合もあります。

コメントについて: ジェネリックスを使用してSoundではなくQuackをメソッドDuck.getSound()に返す場合はどうなりますか?

Birdインターフェースに次のメソッドがあると仮定します。

public interface Bird<T extends Sound> {
    T getSound();
}

次に、次のように実装できます。

private static class Duck implements Bird<Quack> {
    public Quack getSound() { return new Quack(); }
}

これはジェネリックスの1つのユースケースです。実装で具象型を指定できるため、スーパークラスでもその型を使用できます。(Birdインターフェースは、setSound(T)Tの具体的なタイプを知らなくても、Tを使用して、または他の処理を実行できます。)

呼び出し元がインスタンスのタイプを知っているだけの場合、次のBird<? extends Sound>ようにgetSoundを呼び出す必要があります。

Sound birdSound = bird.getSound();

発信者が知っていれば、テストQuackを実行できます。instanceofしかし、発信者が鳥が本当にBird<Quack>、またはそれでさえあることを知っていた場合Duck、彼はこれを書くことができ、それは望み通りにコンパイルされます:

Quack birdSound = bird.getSound();

ただし、注意してください。インターフェイスまたはスーパークラスで使用されるタイプが多すぎると、システムが複雑になりすぎるリスクがあります。Slanecが書いたように、実際の設計を再考して、非常に多くのジェネリックが本当に必要かどうかを確認してください。

私はかつて行き過ぎて、次のようなインターフェイスに基づいて、インターフェイス階層と2つの実装階層になってしまいました。

interface Tree<N extends Node<N>,
               T extends Tree<N, T>> { ... }

interface SearchableTree<N extends SearchableNode<N>,
                         S extends Searcher<N>,
                         T extends SearchableTree<N, S, T>>
    extends Tree<N, T> { ... }

その例に従うことはお勧めしません。;-)

于 2012-06-09T08:26:23.057 に答える
1

私は言います:AAはAを実装します。List<AA> l = list()を定義することにより、B<X>を拡張することを期待します。とにかく、あなたはそのようなコードを書くことによってあなたがどれほど簡単に混乱するかを見るでしょう。これは複雑すぎます。

于 2012-06-09T08:02:38.360 に答える
0

あなたはJavaGenericsについて少し誤解しています。覚えておくべきこと、そしてこれは微妙なことです。aList<Y>はリストの内容ではなく、リスト自体の変更に関するものです。

少し外挿してみましょう。私が持っているinterface Animalと言うinterface Dog extends Animalinterface Cat extends Animal。(これから、さらに多くのクラスとインターフェイスを発明します。)ここで、動物をとして返すメソッドを宣言した場合List<Animal> createList()、次のコードに問題はありません。

List<Animal> litter = createList();
Cat tabby = new Tabby();
litter.add(tabby);
Dog poodle = new Poodle();
litter.add(poodle);

犬は動物であり、猫は動物だからです。アドオンタイプのメソッドシグネチャList<Animal>add(Animal);です。add予想どおり、Animalの任意の有効なインスタンスで呼び出すことができます。ただし、のtypeパラメーターListは、リストの内容を変更または制限するのではなく、リスト自体のタイプを変更します。また、「猫のリスト」は「動物のリスト」ではなく、「犬のリスト」でもありません。メソッドが実際にのインスタンスのみを含むをcreateLitter()返す場合でも、上記のコードは問題ありません。ただし、リストのタイプを「狭く」することはできません。たとえば、これはコンパイルエラーです。new ArrayList<Animal>()Parrot

List<Bird> birds = createList(); // does not compile

それが許可され、createList私たちのタビーを含む「動物のリスト」を返したと想像してみてください。次の場合、クラスキャスト例外が発生します。

Bird leaderOfTheFlock = birds.get(0);

また、リストのタイプを「拡大」することもできません。可能かどうか想像してみてください。

List<Object> things = createList(); // does not compile

これも許可されない理由は、anが。であるため、コードが-new Integer(0)にを追加できるようになったためです。明らかにそれは私たちが望んでいることでもありません、そして同じ理由で-「動物のリスト」は「オブジェクトのリスト」ではありません。のタイプパラメータ「Animal」は、リスト自体のタイプを変更します。ここでは、2つの異なるタイプのリストについて説明しています。これは、その点の最初の結果につながります-ジェネリック型は継承(is-a)階層に従いません。thingsIntegerObjectList<Animal>

あなたが何をしたいのかをもっと知らなければ、ここから進んで関連性を保つのは難しいです。厳しいという意味ではありませんが、何かが機能するかどうかを確認するために、コードにジェネリックスを投げ始めたようです。私はジェネリックスで何年も苦労しました。この微妙な点を説明するブログに出くわした後でも、レッスンを強化するために上記のかなりの数のバリエーションを再現する必要があり、ルールを破った場合にクラスキャストの例外が発生するさまざまな方法を探しました。問題の解決策としては、導入しようとしている厳密な型システムに関してコードの他の部分が明確に定義されておらず、一般的な問題はその症状にすぎない可能性があります。ジェネリックスを減らし、構成と継承にもっと依存するようにしてください。私はまだ一般的なディープエンドを離れることによって時々足で自分自身を撃ちます。ジェネリックスのポイントは、キャストを排除することではなく、コードが型を処理する方法の正確さを検証するための補助として、型情報をコンパイラーが利用できるようにすることです。つまり、ランタイムエラー(クラスキャスト)がソース/コンパイル時エラーに変わるため、コンパイル時に保持するタイプ情報の違いを覚えておくことが重要です(ジェネリックスでも制限されます)。 )および実行時に保持する型情報(インスタンスの完全な型情報)。

于 2013-08-22T07:26:45.203 に答える