次のテスト プログラムは、何か役に立つことを行う、より複雑なプログラムから派生したものです。Eclipse コンパイラーで正常にコンパイルされます。
import java.util.ArrayList;
import java.util.List;
public class InferenceTest
{
public static void main(String[] args)
{
final List<Class<? extends Foo<?, ?>>> classes =
new ArrayList<Class<? extends Foo<?, ?>>>();
classes.add(Bar.class);
System.out.println(makeOne(classes));
}
private static Foo<?, ?> makeOne(Iterable<Class<? extends Foo<?, ?>>> classes)
{
for (final Class<? extends Foo<?, ?>> cls : classes)
{
final Foo<?, ?> foo = make(cls); // javac error here
if (foo != null)
return foo;
}
return null;
}
// helper used to capture wildcards as type variables
private static <A, B, C extends Foo<A, B>> Foo<A, B> make(Class<C> cls)
{
// assume that a real program actually references A and B
try
{
return cls.getConstructor().newInstance();
}
catch (final Exception e)
{
return null;
}
}
public static interface Foo<A, B> {}
public static class Bar implements Foo<Integer, Long> {}
}
ただし、Oracle JDK 1.7 javac では、次のように失敗します。
InferenceTest.java:18: error: invalid inferred types for A,B; inferred type does not
conform to declared bound(s)
final Foo<?, ?> foo = make(cls);
^
inferred: CAP#1
bound(s): Foo<CAP#2,CAP#3>
where A,B,C are type-variables:
A extends Object declared in method <A,B,C>make(Class<C>)
B extends Object declared in method <A,B,C>make(Class<C>)
C extends Foo<A,B> declared in method <A,B,C>make(Class<C>)
where CAP#1,CAP#2,CAP#3 are fresh type-variables:
CAP#1 extends Foo<?,?> from capture of ? extends Foo<?,?>
CAP#2 extends Object from capture of ?
CAP#3 extends Object from capture of ?
1 error
どのコンパイラが正しいですか?
上記の出力の疑わしい側面の 1 つは、CAP#1 extends Foo<?,?>
. 型変数の境界はCAP#1 extends Foo<CAP#2,CAP#3>
. この場合、推論さCAP#1
れた境界は宣言された境界に準拠します。CAP#1
ただし、C は実際には であると推測されるはずですが、エラー メッセージは A と B に関するものであるため、これはおかしなことかもしれません。
26 行目を次のように置き換えると、両方のコンパイラがプログラムを受け入れることに注意してください。
private static <C extends Foo<?, ?>> Foo<?, ?> make(Class<C> cls)
Foo
ただし、キャプチャされたパラメーターの型を参照することはできません。
更新:両方のコンパイラで同様に受け入れられます(ただし、役に立たない)のは次のとおりです。
private static <A, B, C extends Foo<? extends A, ? extends B>>
Foo<? extends A, ? extends B> make(Class<C> cls)
それは本質的にA
とB
を として自明に推論するObject
ため、どのような状況でも明らかに役に立ちません。ただし、javac
ワイルドカード境界でのみ推論を実行し、境界をキャプチャしない以下の私の理論に信憑性を与えます。誰もより良いアイデアを持っていない場合、これが(残念な)答えかもしれません. (更新終了)
この質問全体がTL;DRである可能性が高いことは理解していますが、他の誰かがこの問題に遭遇した場合に備えて続行します...
JLS 7、§15.12.2.7 Inferring Type Arguments Based on Actual Argumentsに基づいて、次の分析を行いました。
A << F
、A = F
、またはの形式の制約が与えられた場合A >> F
:
最初に、フォームの制約が 1 つありますA << F
。これは、型がメソッド呼び出しの変換によってA
型に変換可能であることを示します ( §5.3 )。ここでは、 とです。他の制約形式 (および) は、推論アルゴリズムが再帰的に発生することに注意してください。F
A
Class<CAP#1 extends Foo<CAP#2, CAP#3>>
F
Class<C extends Foo<A, B>>
A = F
A >> F
次に、次のルールC
であると推測する必要があります。CAP#1
(2.) それ以外の場合、制約が次の形式の場合
A << F
:
- が を含む型式である場合、 が型式であるという
F
形式のスーパータイプを有する場合、このアルゴリズムは制約 に再帰的に適用されます。G<..., Yk-1, U, Yk+1, ...>
U
Tj
A
G<..., Xk-1, V, Xk+1, ...>
V
V = U
ここでG
はClass
、 、U
およびTj
はC
、およびV
ですCAP#1
。に再帰的に適用すると、次CAP#1 = C
の制約が発生するはずC = CAP#1
です。
(3.) それ以外の場合、制約が次の形式の場合
A = F
:
- の場合
F = Tj
、制約Tj = A
が暗示されます。
ここまでの分析は、javac の出力と一致しているようです。A
おそらく、分岐点は、 と を推論しようとし続けるかどうかB
です。たとえば、このルールが与えられた場合
- 次のいずれかのスーパー タイプを
F
持つifG<..., Yk-1, ? extends U, Yk+1, ...>
U
Tj
A
G<..., Xk-1, V, Xk+1, ...>
、V
は型式です。G<..., Xk-1, ? extends V, Xk+1, ...>
.次に、このアルゴリズムが再帰的に制約に適用されます
V << U
。
CAP#1
がワイルドカード (キャプチャされたもの) であると見なされる場合、この規則が適用され、 U
asFoo<A, B>
およびV
asを使用して推論が再帰的に続行されFoo<CAP#2, CAP#3>
ます。上記のように、これにより と が得A = CAP#2
られB = CAP#3
ます。
ただし、CAP#1
が単なる型変数の場合、どのルールもその境界を考慮していないようです。おそらく、仕様のセクションの最後にあるこの譲歩は、そのような場合を指しています。
型推論アルゴリズムは、実際にうまく機能するように設計されたヒューリスティックと見なす必要があります。目的の結果を推測できない場合は、代わりに明示的な型パラメーターを使用できます。
明らかに、ワイルドカードを明示的な型パラメーターとして使用することはできません。:-(