51

これをどのように機能させますか:

public class Frankenstein<T extends IHuman, IMonster>{
}

作らずに

public interface Weirdo extends Ihuman, IMonster{
}

編集

なぜこれが機能しないのですか?

public <T> void mapThis(
        Class<? extends MyClass<T>> key, Class<? extends T & IDisposable> value) {

}

コンパイラメッセージClass<? extends T & IDisposable>がエラーとしてマークされます。

4

2 に答える 2

116

Reimeusは、編集で求めていることは不可能であるとすでに指摘しています。その理由について少し詳しく説明したいと思います。

次のものを使用できると思います。

public <T, U extends T & IDisposable> void mapThis(
        Class<? extends MyClass<T>> key,
        Class<? extends U> value
) { ... }

実際、私がこの投稿を最初に見たとき、それが私の頭に浮かんだことです。しかし、これは実際にはコンパイラエラーを引き起こします:

型変数の後に他の境界を続けることはできません

理由を説明するために、このエラーに関するVictorRudometovによるOracleブログの投稿を引用したいと思います。

この事実は必ずしも明確ではありませんが、それは真実です。次のコードはコンパイルしないでください。

interface I {}

class TestBounds <U, T extends U & I> {

}

JLS第4章「型、値、および変数」セ​​クション4.4「型変数」には次よう記載されているためです。。したがって、T extends U、T extends SomeClass&Iを使用できますが、 T extends U&Iは使用できません。このルールは、メソッドとコンストラクターの型変数と境界を含むすべての場合に適用されます。

この制限の理由は、密接に関連する投稿で詳しく説明されています。複数の境界を持つ型パラメーターで型引数を使用できないのはなぜですか。

要約すると、「特定の厄介な状況が発生するのを防ぐ」ために制限が課されました(JLS§4.9)。

どんな厄介な状況?Chris Povirkによる回答は、次の1つを説明しています。

【制限の理由】不正な型を指定する可能性があります。具体的には、異なるパラメーターを使用して汎用インターフェースを2回拡張します。工夫されていない例を思いつくことはできませんが、次のようになります。

/** Contains a Comparator<String> that also implements the given type T. */
class StringComparatorHolder<T, C extends T & Comparator<String>> {
  private final C comparator;
  // ...
}

void foo(StringComparatorHolder<Comparator<Integer>, ?> holder) { ... }

今はとholder.comparatorです。Comparator<Integer>Comparator<String>

クリスはまた、この言語制限に異議を唱えるバグであるSunバグ4899305を指摘しています。次のコメントで修正されないため、クローズされました。

型変数の後に型変数または(おそらくパラメーター化された)インターフェースが続く可能性がある場合、相互に再帰的な型変数が存在する可能性が高く、処理が非常に困難です。境界が単にパラメータ化されたタイプである場合、物事はすでに複雑<S,R extends Comparable<S>>です。したがって、境界は今は変更されません。javacとEclipseはどちらもそれに同意しS&TS&Comparable<S>違法です。

したがって、これらが制限の背後にある理由です。特にジェネリックメソッド(あなたの質問が関係している)に対処するために、型推論は理論的にはそのような境界がとにかく無意味になることをさらに指摘したいと思います。

上記の架空の署名で宣言された型パラメーターを再検討すると、次のようになります。

<T, U extends T & IDisposable>

呼び出し元がとを明示的に指定していないと仮定するTU、これは次のように減らすことができます。

<T, U extends Object & IDisposable>

またはこれだけ(微妙な違いですが、それは別のトピックです):

<T, U extends IDisposable>

これはT、境界がないためです。したがって、渡される引数のタイプに関係なく、少なくともT常に解決できるため、そうすることができます。ObjectU

T戻って、制限されていると言いましょう。

<T extends Foo, U extends T & IDisposable>

これは同じ方法で減らすことがFooできます(クラスまたはインターフェースの場合もあります)。

<T extends Foo, U extends Foo & IDisposable>

その推論に基づいて、あなたが達成しようとしている構文は、呼び出し元をより具体的な引数に制限する限り無意味です。

Java 8より前の補遺:

Java 8より前、実行しようとしていることのユースケースがあります。コンパイラーがジェネリックメソッドタイプパラメーターを推測する方法に制限があるため、上記の理由でウィンドウから外れます。次の一般的な方法を取ります。

class MyClass {
    static <T> void foo(T t1, T t2) { }
}

これは、「同じタイプ」の2つのパラメーターを受け取るメソッドを作成しようとする初心者のよくある間違いです。もちろん、継承の仕組みのために無意味です。

MyClass.foo("asdf", 42); // legal

ここでTは、次のように推測されます-これは、型パラメーターObjectを単純化することについての以前の推論と一致します。mapThis目的の型チェックを実行するには、型パラメーターを手動で指定する必要があります。

MyClass.<String>foo("asdf", 42); // compiler error

ただし、ここでユースケースが登場し始めます。これは、境界がずらされた複数の型パラメーターでは別の問題です。

class MyClass {
    static <T, U extends T> void foo(T t, U u) { }
}

今、この呼び出しエラー:

MyClass.foo("asdf", 42); // compiler error

テーブルが変わりました-コンパイルするには、型パラメーターを手動で緩和する必要があります。

MyClass.<Object, Object>foo("asdf", 42); // legal

これは、コンパイラがメソッドタイプパラメータを推測する方法が限られているために発生します。このため、あなたが達成したかったことは、実際には呼び出し元の引数を制限するアプリケーションを持っていたでしょう。

ただし、この問題はJava 8で修正されたようでMyClass.foo("asdf", 42)、エラーなしでコンパイルされるようになりました(これを指摘してくれたRegentに感謝します)。

于 2012-11-07T00:41:52.033 に答える
4

これらの(非常にまれな)状況で使用するハックを共有すると思いました。

    /**
     * This is a type-checking method, which gets around Java's inability
     * to handle multiple bounds such as "V extends T & ContextAware<T>".
     *
     * @param value initial value, which should extends T
     * @param contextAware initial value, which should extend ContextAware<T>
     * @return a new proxy object
     */
    public T blah(T value, ContextAware<T> contextAware) {
        if (value != contextAware) {
            throw new IllegalArgumentException("This method expects the same object for both parameters.");
        }

        return blah(value);
    }

したがって、満たそうとしている境界ごとに同じオブジェクトを要求することで、コンパイル時の型チェックと、すべてを実行する単一のオブジェクトを取得できます。確かに、各パラメーターに同じオブジェクトを渡すのは少しばかげていますが、これは「内部」コードで非常に安全かつ快適に行います。

于 2013-06-28T15:04:56.747 に答える