ジェネリック メソッドの型パラメーターは、 などの境界によって制限できますが、extends Foo & Bar
最終的には呼び出し元によって決定されます。を呼び出すgetFooBar()
と、呼び出しサイトは既に何T
に解決されているかを認識しています。多くの場合、これらの型パラメーターはコンパイラーによって推論されるため、通常は次のように指定する必要はありません。
FooBar.<FooAndBar>getFooBar();
しかし、T
が であると推測される場合でもFooAndBar
、それは実際に舞台裏で起こっていることです。
したがって、あなたの質問に答えるには、次のような構文です。
Foo&Bar bothFooAndBar = FooBar.getFooBar();
実際には決して役に立ちません。その理由は、呼び出し元がすでに何が何でT
あるかを知っている必要があるためです。いずれT
かの具体的なタイプです:
FooAndBar bothFooAndBar = FooBar.<FooAndBar>getFooBar(); // T is FooAndBar
または、T
未解決の型パラメーターであり、そのスコープ内にいます。
<U extends Foo & Bar> void someGenericMethod() {
U bothFooAndBar = FooBar.<U>getFooBar(); // T is U
}
その別の例:
class SomeGenericClass<V extends Foo & Bar> {
void someMethod() {
V bothFooAndBar = FooBar.<V>getFooBar(); // T is V
}
}
技術的には、これで答えは終わりです。getFooBar
しかし、あなたのサンプルメソッドは本質的に安全ではないことも指摘したいと思います. メソッドではなく、呼び出し元が何になるかを決定することを忘れないでくださいT
。getFooBar
は に関連するパラメータをとらないためT
、タイプが eraseであるため、ヒープ汚染の危険を冒して、未チェックのキャストを作成することによって戻るnull
か、「嘘をつく」しかありません。典型的な回避策は、たとえば、引数を取ることです。getFooBar
Class<T>
FooFactory<T>
アップデート
getFooBar
の呼び出し元は常に何が何であるかを知っている必要があると主張したとき、私は間違っていたことがわかりましたT
。@MiserableVariable が指摘するように、ジェネリック メソッドの型引数が具体的な型または型変数ではなく、ワイルドカード キャプチャであると推論される状況がいくつかあります。プロキシを使用して不明な点を強調する実装の優れた例については、彼の回答を参照してください。getFooBar
T
コメントで説明したように、を使用した例getFooBar
では、推論に引数が必要ないため、混乱が生じましたT
。特定のコンパイラはコンテキストレス呼び出しでエラーをスローしますgetFooBar()
が、他のコンパイラはそれで問題ありません。一貫性のないコンパイル エラー (および呼び出しが違法であるという事実) が私の主張を裏付けていると思いましFooBar.<?>getFooBar()
たが、これらは赤ニシンであることが判明しました。
@MiserableVariable の回答に基づいて、混乱を取り除くために、引数付きのジェネリック メソッドを使用する新しい例をまとめました。インターフェイスFoo
とBar
実装があるとしますFooBarImpl
。
interface Foo { }
interface Bar { }
static class FooBarImpl implements Foo, Bar { }
Foo
また、 and を実装する何らかの型のインスタンスをラップする単純なコンテナ クラスもありますBar
。unwrap
を受け取り、FooBarContainer
その指示対象を返すばかげた静的メソッドを宣言します。
static class FooBarContainer<T extends Foo & Bar> {
private final T fooBar;
public FooBarContainer(T fooBar) {
this.fooBar = fooBar;
}
public T get() {
return fooBar;
}
static <T extends Foo & Bar> T unwrap(FooBarContainer<T> fooBarContainer) {
return fooBarContainer.get();
}
}
ここで、次のワイルドカード パラメータ化タイプがあるとしFooBarContainer
ます。
FooBarContainer<?> unknownFooBarContainer = ...;
に渡すことができunknownFooBarContainer
ますunwrap
。これは、呼び出しサイトが何であるかを知らないため、以前の主張が間違っていたことを示していますT
- それが境界内の何らかのタイプであることだけですextends Foo & Bar
。
FooBarContainer.unwrap(unknownFooBarContainer); // T is a wildcard capture, ?
前述したようunwrap
に、ワイルドカードを使用した呼び出しは違法です。
FooBarContainer.<?>unwrap(unknownFooBarContainer); // compiler error
これは、ワイルドカード キャプチャが互いに一致しないためであると推測することしかできません?
。呼び出しサイトで提供される引数はあいまいであり、型のワイルドカードと具体的に一致する必要があるとは言えませんunknownFooBarContainer
。
したがって、OPが求めている構文のユースケースは次のとおりです。unwrap
on を呼び出すとunknownFooBarContainer
、 type の参照が返されます? extends Foo & Bar
。その参照をFoo
orBar
に代入できますが、両方には代入できません:
Foo foo = FooBarContainer.unwrap(unknownFooBarContainer);
Bar bar = FooBarContainer.unwrap(unknownFooBarContainer);
何らかの理由unwrap
でコストが高く、一度だけ呼び出したい場合は、次のようにキャストする必要があります。
Foo foo = FooBarContainer.unwrap(unknownFooBarContainer);
Bar bar = (Bar)foo;
したがって、これが仮想構文が役立つ場所です。
Foo&Bar fooBar = FooBarContainer.unwrap(unknownFooBarContainer);
これは、かなりあいまいな使用例の 1 つにすぎません。このような構文を許可することには、良い面と悪い面の両方で、かなり広範囲にわたる影響があります。必要のないところに悪用の余地が生まれることになり、言語設計者がそのようなものを実装しなかった理由は完全に理解できます。でも、考えてみると面白いと思います。
ヒープ汚染に関する注意
(主に @MiserableVariable 用) のような安全でないメソッドgetFooBar
がヒープ汚染を引き起こす方法とその影響についてのウォークスルーです。次のインターフェースと実装があるとします。
interface Foo { }
static class Foo1 implements Foo {
public void foo1Method() { }
}
static class Foo2 implements Foo { }
getFoo
に似てgetFooBar
いますが、この例では簡略化された unsafe method を実装しましょう。
@SuppressWarnings("unchecked")
static <T extends Foo> T getFoo() {
//unchecked cast - ClassCastException is not thrown here if T is wrong
return (T)new Foo2();
}
public static void main(String[] args) {
Foo1 foo1 = getFoo(); //ClassCastException is thrown here
}
ここで、 newFoo2
が にキャストされるとT
、それは「未チェック」になります。つまり、型消去のために、この場合はT
wasであるため失敗する必要があるにもかかわらず、ランタイムは失敗する必要があることを認識しませんFoo1
。代わりに、ヒープは「汚染」されています。つまり、参照が許可されるべきではないオブジェクトを指しています。
インスタンスが reifiable type を持つ参照にFoo2
割り当てられようとすると、メソッドが返された後に失敗が発生します。foo1
Foo1
おそらく、「よし、メソッドではなく呼び出しサイトで爆発した、大したことだ」と考えているでしょう。しかし、より多くのジェネリックが関与すると、簡単に複雑になる可能性があります。例えば:
static <T extends Foo> List<T> getFooList(int size) {
List<T> fooList = new ArrayList<T>(size);
for (int i = 0; i < size; i++) {
T foo = getFoo();
fooList.add(foo);
}
return fooList;
}
public static void main(String[] args) {
List<Foo1> foo1List = getFooList(5);
// a bunch of things happen
//sometime later maybe, depending on state
foo1List.get(0).foo1Method(); //ClassCastException is thrown here
}
これで、コール サイトで爆発しなくなりました。中身が慣れてくると爆破しfoo1List
ます。これは、例外スタック トレースが実際の問題を示していないため、ヒープ汚染のデバッグが難しくなっている理由です。
呼び出し元がジェネリック スコープ自体にある場合は、さらに複雑になります。を取得する代わりに を取得し、それを に入れ、さらに別のメソッドに返すことを想像してみてくださいList<Foo1>
。あなたは私が望む考えを得る。List<T>
Map<K, List<T>>