nra*_*ner 33 java generics type-inference generic-method
为什么以下代码编译?该方法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");
}
}
Run Code Online (Sandbox Code Playgroud)
返回类型是否仍然是一个实例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;
Run Code Online (Sandbox Code Playgroud)
编辑:替换String与一致Integer.
Rad*_*def 21
这实际上是一种合法的类型推断*.
我们可以将其减少到以下示例(Ideone):
interface Foo {
<F extends Foo> F bar();
public static void main(String[] args) {
Foo foo = null;
String baz = foo.bar();
}
}
Run Code Online (Sandbox Code Playgroud)
允许编译器推断(无意义的,真正的)交集类型,String & Foo因为它Foo是一个接口.对于问题中的示例,Integer & IElement推断出来.
这是荒谬的,因为转换是不可能的.我们不能自己做这样的演员:
// won't compile because Integer is final
Integer x = (Integer & IElement) element;
Run Code Online (Sandbox Code Playgroud)
类型推断基本上适用于:
在算法结束时,每个变量都根据绑定集解析为交集类型,如果它们有效,则调用将进行编译.
该过程从8.1.3开始:
推理开始时,通常从类型参数声明列表和关联的推理变量生成绑定集.这样的绑定集如下构造.对于每个l(1≤l≤p):
P1, ..., Pp?1, ..., ?p
[...]
否则,对于在TypeBound中
T分隔的每种类型,绑定将出现在集合[...]中.&?l <: T[P1:=?1, ..., Pp:=?p]
所以,这意味着首先编译器以一个绑定开始F <: Foo(这意味着F是一个子类型Foo).
移至18.5.2,将考虑返回目标类型:
如果调用是一个多义表达式,那么让它
R成为返回类型m,让它T成为调用的目标类型,然后:
[...]
否则,约束公式
‹R ? ? T›被减少并与[绑定集]合并.
约束公式‹R ? ? T›减少到另一个界限R ? <: T,所以我们有F <: String.
之后根据18.4解决了这些问题:
[...] 为每个人定义候选实例:
Ti?i
- 否则,哪里有适当的上限,.
?iU1, ..., UkTi = glb(U1, ..., Uk)边界与当前边界集合并在一起.
?1 = T1, ..., ?n = Tn
回想一下,我们的界限是F <: Foo, F <: String.glb(String, Foo)被定义为String & Foo.这显然是glb的合法类型,它只需要:
它是一个编译时间错误,如果,对于任何两个类(未接口)和,不是的一个子类,或反之亦然.
ViVjViVj
最后:
如果解析成功与推理变量的实例化,让我们替换.然后:
T1, ..., Tp?1, ..., ?p?'[P1:=T1, ..., Pp:=Tp]
- 如果不加以控制的转换是不必要的方法可应用于,那么该调用键入
m通过施加获得?'到的类型m.
因此,该方法String & Foo作为类型调用F.我们当然可以将其分配给a String,因此不可能将a转换Foo为a 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 {}
Run Code Online (Sandbox Code Playgroud)
发出如下错误已经完全有效:
FooImplMap m = ...;
m.put("b", new Bar());
Biz b = m.get("b"); // casting Bar to Biz
Run Code Online (Sandbox Code Playgroud)
因此,我们也可以做的事实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;
}
Run Code Online (Sandbox Code Playgroud)
这是A-OK,因为Optional.empty()既不能生产也不能消费T.