[英]Can Java infer type arguments from type parameter bounds?
以下测试程序源自一个更复杂的程序,它可以执行一些有用的操作。 它与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
哪个编译器是对的?
上面输出的一个可疑方面是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基于实际参数推断类型参数 ,我做了以下分析:
给定形式
A << F
,A = F
或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.)否则,如果约束的形式为
A << F
:
- 如果
F
的格式为G<..., Yk-1, U, Yk+1, ...>
,其中U
是涉及Tj
的类型表达式,那么如果A
具有格式G<..., Xk-1, V, Xk+1, ...>
的超类型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, ...>
G<..., Yk-1, ? extends U, Yk+1, ...>
,其中U
涉及Tj
,然后如果A
的超类型是以下之一:
G<..., Xk-1, V, Xk+1, ...>
,其中V
是类型表达式。G<..., Xk-1, ? extends V, Xk+1, ...>
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
只是一个类型变量,那么这些规则似乎都没有考虑它的界限。 也许在规范部分末尾的这种让步引用了这样的情况:
类型推断算法应被视为启发式算法,旨在在实践中表现良好。 如果它无法推断出期望的结果,则可以使用显式类型参数。
显然,通配符不能用作显式类型参数。 :-(
问题是您从以下推理约束开始:
class<#1>, #1 <: Foo<?, ?>
这为您提供了C的解决方案,即C =#1。
然后你需要检查C是否符合声明的边界 - C的边界是Foo,所以你最终得到这个检查:
#1 <: Foo<A,B>
可以改写为
Bound(#1) <: Foo<A, B>
因此:
Foo<?, ?> <: Foo<A, B>
现在,编译器在这里进行LHS的捕获转换(这里是生成#2和#3的地方):
Foo<#2, #3> <: Foo<A, B>
意思是
A = #2
B = #3
所以,我们的解决方案是{A =#2,B =#3,C =#1}。
这是一个有效的解决方案? 为了回答这个问题,我们需要在类型替换后检查推断类型是否与推理变量边界兼容,因此:
[A:=#2]A <: Object
#2 <: Object - ok
[B:=#3]B <: Object
#3 <: Object - ok
[C:=#1]C <: [A:=#2, B:=#3]Foo<A, B>
#1 <: Foo<#2, #3>
Foo<?, ?> <: Foo<#2, #3>
Foo<#4, #5> <: Foo<#2, #3> - not ok
因此错误。
当涉及推理和捕获类型之间的相互作用时,规范是不明确的,因此在不同编译器之间切换时有不同的行为是很正常的(但不是很好!)。 然而,从编译器角度和JLS角度来看,其中一些问题正在得到解决,所以这样的问题应该在中期得到解决。
我注意到的两件事:
CAP#1
不是通配符,因为捕获转换 ,它是一个类型变量。
在第一步,JLS提到U
是类型表达式,而Tj
是类型参数 。 JLS没有明确定义类型表达式是什么,但我的直觉是它包含了类型参数的边界。 如果是这种情况,则U
将是C extends Foo<A,B>
并且V
将是CAP#1 extends Foo<CAP#2, CAP#3>
。 遵循类型推断算法:
V = U
- > C = CAP#1
和Foo<CAP#2, CAP#3> = Foo<A, B>
您可以继续将类型推断算法应用于上述,最终将得到A= CAP#2
和B=CAP#3
。
我相信你已经发现了Oracle编译器的一个错误
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.