Java 可以从类型参数边界推断类型参数吗?
以下测试程序派生自执行有用操作的更复杂的程序。它使用 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
哪个编译器是正确的?
上述输出的一个可疑方面是 。我希望类型变量边界是.如果是这种情况,则推断的边界将符合声明的边界。但是,这可能是一个红鲱鱼,因为C确实应该被推断为,但错误消息是关于A和B的。CAP#1 extends Foo<?,?>
CAP#1 extends Foo<CAP#2,CAP#3>
CAP#1
CAP#1
请注意,如果我将第 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 基于实际参数推断类型参数,我进行了以下分析:
给定形式 、 或 的约束:
A << F
A = F
A >> F
最初,我们有一个形式约束,它指示类型可以通过方法调用转换转换为类型(§5.3)。这里,是 和 是 。请注意,其他约束形式( and ) 仅在推理算法递归时出现。A << F
A
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
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
如果 被认为是通配符(它是其捕获),则此规则适用,并且推理继续递归地使用 as 和 as 。如上所述,这将产生 和 。CAP#1
U
Foo<A, B>
V
Foo<CAP#2, CAP#3>
A = CAP#2
B = CAP#3
但是,如果只是一个类型变量,那么似乎没有一个规则考虑它的边界。也许规范中该部分末尾的此让步是指以下情况:CAP#1
类型推理算法应被视为启发式算法,旨在在实践中表现良好。如果它无法推断出所需的结果,则可以改用显式类型参数。
显然,通配符不能用作显式类型参数。:-(