2015-04-16 31 views
32

为什么下面的代码会被编译? 方法IElement.getX(String)返回类型为IElement的实例或其子类。 Main类中的代码调用getX(String)方法。编译器允许将返回值存储到Integer类型的变量(显然不在IElement的层次结构中)。为什么带有绑定的泛型方法返回任何类型?

public interface IElement extends CharSequence { 
    <T extends IElement> T getX(String value); 
} 

public class Main { 
    public void example(IElement element) { 
    Integer x = element.getX("x"); 
    } 
} 

不应该返回类型仍然是IElement一个实例 - 甚至是类型擦除后?

getX(String)方法的字节码是:

public abstract <T extends IElement> T getX(java.lang.String); 
flags: ACC_PUBLIC, ACC_ABSTRACT 
Signature: #7       // <T::LIElement;>(Ljava/lang/String;)TT; 

编辑:Integer替换String一致。

+1

这只是没有任何意义。 – Radiodef

+1

Java版本是:1.7.0_45 32位 – nrainer

+0

CharSequence可分配给字符串..有什么问题? –

回答

20

这实际上是一个合法的类型推断*。

我们可以这样降低到下面的例子(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

当推理开始时,通常是从类型参数声明P1, ..., Pp和相关联的推理变量α1, ..., αp列表生成的绑定集合。这样的绑定集合构造如下。对于每个升(1  ≤ 升 ≤  P)

  • [&hellip;]

  • 否则,对于每个类型T通过&TypeBound限定,结合的αl <: T[P1:=α1, ..., Pp:=αp]出现在集[&hellip;]中。

所以,这意味着第一编译器以一个结合的F <: Foo(这意味着FFoo亚型)开始。

移动到18.5.2,返回目标类型被考虑:

如果调用是聚表达,[&hellip;]让Rm返回类型,让T是调用的目标类型,然后:

  • [&hellip;]

  • 否则,约束式‹R θ → T›被缩小并与[绑定集合]合并。

约束公式‹R θ → T›被减少到另一个必然的R θ <: T,所以我们有F <: String

在这些后来获得根据18.4解决:

[&hellip;]的候选实例Ti为每个αi定义:

  • 否则,在αi有适当的上限U1, ..., UkTi = glb(U1, ..., Uk)

边界α1 = T1, ..., αn = Tn与当前的绑定集合在一起。

回想一下,我们的边界集是F <: Foo, F <: Stringglb(String, Foo)定义为String & Foo。这显然是对glb一个合法的类型,只要求:

这是一个编译时错误,如果,对于任何两个未接口ViVjVi不是一个子类别Vj或反之亦然。

最后:

如果分辨率与推理变量α1, ..., αp实例T1, ..., Tp成功,让θ'是替代[P1:=T1, ..., Pp:=Tp]。然后:

  • 如果未经检查的转化率不必要的方法是适用的,则m调用类型是通过施加到θ'm类型获得。

该方法因此具有String & Foo援引为的F类型。我们当然可以将其分配到String,因此不可能将Foo转换为String

显然不考虑String/Integer是最终类的事实。


*注:类型擦除是/是完全无关的问题。另外,虽然这也编译在Java 7上,但我认为可以合理地说我们不必担心那里的规范。 Java 7的类型推断实质上是Java 8的一个不太复杂的版本。它编译出于类似的原因。


作为附录,而奇怪的,这将有可能不会引起那是不存在的问题。编写一个返回类型只从返回目标中推断的泛型方法是很有用的,因为只有null可以从这种方法返回而不需要转换。

假设的例子中,我们有一些地图模拟存储特定接口的亚型:

它已经完全有效的,使一个错误,如下列:

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

+0

问题可能是'T扩展[接口]'隐式意味着'T扩展对象和[接口]'。由于String是一个对象,编译器不会报告问题。 – Clashsoft

+0

@Radiodef - *原始类型*!这就是我期待的短语! – OldCurmudgeon

+2

@OldCurmudgeon我可以向你保证,现在在这个问答中没有任何原始类型。如果有的话,无论如何会发生不同的事情。我们实际上遇到错误。 http://ideone.com/SeZ09H – Radiodef

相关问题