这实际上是一个合法的类型推断*。
我们可以将其简化为以下示例(Ideone):
interface Foo {
<F extends Foo> F bar();
public static void main(String[] args) {
Foo foo = null;
String baz = foo.bar();
}
}
允许编译器推断(实际上是荒谬的)交集类型,因为它是一个接口。对于问题中的示例,是推断出来的。String & Foo
Foo
Integer & IElement
这是荒谬的,因为转换是不可能的。我们自己做不到这样的演员阵容:
// won't compile because Integer is final
Integer x = (Integer & IElement) element;
类型推断基本上适用于:
- 每个方法的类型参数的一组推理变量。
- 必须符合的一组边界。
- 有时是约束,这些约束被简化为边界。
在算法结束时,每个变量都根据绑定集解析为交集类型,如果它们有效,则编译调用。
该过程从 8.1.3 开始:
当推理开始时,绑定集通常是从类型参数声明和关联的推理变量的列表生成的。这样的绑定集构造如下。对于每 l (1 ≤ l ≤ p):P1, ..., Pp
α1, ..., αp
因此,这意味着首先编译器以 bound of 开头(这意味着 是 的子类型)。F <: Foo
F
Foo
移动到 18.5.2,将考虑返回目标类型:
如果调用是 poly 表达式,则 [...] 设为 的返回类型 ,设为调用的目标类型,然后:R
m
T
约束公式被简化为 的另一个边界,所以我们有 。‹R θ → T›
R θ <: T
F <: String
稍后根据 18.4 解决这些问题:
[...]为每个实例定义了一个候选实例化:Ti
αi
- 否则,其中 具有适当的上限 , 。
αi
U1, ..., Uk
Ti = glb(U1, ..., Uk)
边界与当前绑定集合并。α1 = T1, ..., αn = Tn
回想一下,我们的边界集是 。 定义为 。这显然是 glb 的合法类型,它只需要:F <: Foo, F <: String
glb(String, Foo)
String & Foo
如果对于任何两个类(不是接口)和 不是 的子类,反之亦然,则这是一个编译时错误。Vi
Vj
Vi
Vj
最后:
如果解析成功,则使用推理变量的实例化,设为替换 。然后:T1, ..., Tp
α1, ..., αp
θ'
[P1:=T1, ..., Pp:=Tp]
- 如果不需要未经检查的转换即可使方法适用,则通过应用于 的类型来获得 的调用类型。
m
θ'
m
因此,调用该方法时,其类型为 。我们当然可以将其分配给 a,从而不可能将 a 转换为 a 。String & Foo
F
String
Foo
String
显然没有考虑 / 是最终类的事实。String
Integer
* 注意:类型擦除与问题完全无关。
另外,虽然这也可以在Java 7上进行编译,但我认为可以合理地说,我们不必担心那里的规范。Java 7的类型推断本质上是Java 8的不太复杂的版本。它出于类似的原因进行编译。
作为附录,虽然很奇怪,但这可能永远不会引起尚未出现的问题。编写一个泛型方法,其返回类型仅从返回目标推断出来,这很少有用,因为只能从这样的方法返回而不进行强制转换。null
例如,假设我们有一些映射模拟,它存储特定接口的子类型:
interface FooImplMap {
void put(String key, Foo value);
<F extends Foo> F get(String key);
}
class Bar implements Foo {}
class Biz implements Foo {}
犯如下错误已经是完全有效的:
FooImplMap m = ...;
m.put("b", new Bar());
Biz b = m.get("b"); // casting Bar to Biz
因此,我们也可以这样做的事实并不是错误的新可能性。如果我们像这样编写代码,那么一开始就可能已经不健全了。Integer i = m.get("b");
通常,只有在没有理由绑定目标类型的情况下,才应仅从目标类型推断类型参数,例如 和:Collections.emptyList()
Optional.empty()
private static final Optional<?> EMPTY = new Optional<>();
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
这是 A-OK,因为既不能生产也不能消耗 .Optional.empty()
T