web-dev-qa-db-ja.com

Java generics-Genericを作成して2つのインターフェースを拡張する

この作業をどのように行いますか:

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>エラーとして。

42
Ilya Gazman

レイメウスはすでに、あなたが編集で求めていることは不可能だと指摘しました。理由を少し拡大したいと思います。

あなたは次のものを使用できると思うでしょう:

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

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

型変数の後に他の境界が続くことはできません

その理由を説明するために、Victor Rudometovによるこのエラーについて Oracle Blogsの投稿 を引用したいと思います。

この事実は必ずしも明確ではありませんが、真実です。次のコードはコンパイルできません。

_interface I {}_

_class TestBounds <U, T extends U & I> {_

_}_

JLS第4章「型、値、および変数」セ​​クション4.4型変数の状態:「境界はeither型変数で構成されているため、またはクラスまたはインターフェイスタイプTの後にさらにインターフェイスタイプIが続く可能性がある1 、 ...、 私n "。したがって、TはUを拡張し、TはSomeClassとIを拡張しますが、Tは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>_です。

また、Chrisは Sun bug 4899305 を指していますが、これはこの言語の制限に反するバグでした。次のコメントで、Wo n't Fixとして閉じられました。

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

これらが制限の背後にある理由です。 (あなたの質問が懸念している)具体的な一般的な方法に対処するために、型推論が理論的にはそのような境界をとにかく無意味にさせることをさらに指摘したいと思います。

上記の仮想署名で宣言された型パラメーターを再検討すると:

_<T, U extends T & IDisposable>
_

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

_<T, U extends Object & IDisposable>
_

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

_<T, U extends IDisposable>
_

これは、Tに境界がないため、どのタイプの引数が渡されても、Tは常に少なくともObjectに解決できるため、Uもできるためです。

戻って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
_

ここで、TObjectであると推測されます。これは、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に感謝します)。

102
Paul Bellora

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

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

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

3
Ben