以下測試程序是從更復雜的程序派生而來的,它可以做一些有用的事情。它使用Eclipse編譯器成功編譯。Java可以從類型參數邊界推斷出類型參數嗎?
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
哪個編譯器是正確的?
上面輸出的一個可疑方面是CAP#1 extends Foo<?,?>
。我期望類型變量的範圍是CAP#1 extends Foo<CAP#2,CAP#3>
。如果是這種情況,那麼CAP#1
的推斷界限將符合所聲明的界限。然而,這可能是一個紅色的鯡魚,因爲C確實應推斷爲CAP#1
,但錯誤信息是關於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
類型可以通過方法調用轉換(§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。)否則,如果約束的形式爲:
- 如果
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
具有形式G<..., 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
作爲Foo<A, B>
和V
爲Foo<CAP#2, CAP#3>
繼續遞歸。如上所述,這將產生A = CAP#2
和B = CAP#3
。
但是,如果CAP#1
只是一個類型變量,那麼沒有任何規則似乎考慮它的邊界。規範中該部分末尾的這種讓步指的是這種情況:
類型推斷算法應該被視爲一種啓發式算法,旨在在實踐中表現良好。如果它無法推斷所需的結果,則可以使用顯式類型參數。
顯然,通配符不能用作顯式類型參數。 :-(
您正在使用什麼版本javac'的'? 'javac -version' – Jeffrey
@Jeffrey:比1.7更具體嗎? 'javac 1.7.0_25' –
@BevynQ:是的,那個例子「解決方法」就在我的問題中。然而,這是沒有用的,因爲'make'的要點是捕獲通配符,例如用於在進一步的方法調用中表示相同類型的多個實例或使用這些類型參數創建其他對象實例。這個問題與結果賦予'Foo ,?>'無關;該部分適用於任一編譯器。 –