我最近在重构代码时遇到了这个问题:
下面的方法“getList()”具有参数化返回类型。在此之下,我已经设置了三种尝试将<T>
隐式绑定到<Integer>
的方法。
我无法弄清楚为什么前两个编译运行正常,而第三个(bindViaMethodInvocation)甚至不编译。
任何线索?
在StackOverflow上寻找类似的问题时,我遇到了这个问题: Inferred wildcard generics in return type。那里的答案(credit Laurence Gonsalves)有几个有用的参考链接来解释应该发生的事情: “这里的问题(正如你的建议)是编译器正在执行Capture Conversion。我相信这是JLS §15.12.2.6 of the JLS的结果。”
package stackoverflow;
import java.util.*;
public class ParameterizedReturn
{
// Parameterized method
public static <T extends Object> List<T> getList()
{
return new ArrayList<T>();
}
public static List<Integer> bindViaReturnStatement()
{
return getList();
}
public static List<Integer> bindViaVariableAssignment()
{
List<Integer> intList = getList();
return intList;
}
public static List<Integer> bindViaMethodInvocation()
{
// Compile error here
return echo(getList());
}
public static List<Integer> echo(List<Integer> intList)
{
return intList;
}
}
答案 0 :(得分:7)
前两个方法在受赋值转换约束的上下文中使用getList()
- 对List<Integer>
的赋值或返回List<Integer>
的方法的return语句。对于bindViaMethodInvocation
,不是为真 - 使用表达式作为方法参数 不受赋值转换的影响。
如果没有从实际参数的类型推断出任何方法的类型参数,现在推断它们如下。
- 如果方法结果发生在将要进行赋值转换(第5.2节)的上下文中,则将R作为方法的声明结果类型,并让R'= R [T1 = B (T1)...... Tn = B(Tn)]其中B(Ti)是上一节中Ti的推断类型,如果没有推断出类型,则为Ti。
JLS不清楚为什么返回语句在这里计算。我能找到的最近的是14.17:
带有Expression的return语句必须包含在声明为返回值(第8.4节)或发生编译时错误的方法声明中。 Expression必须表示某种类型T的变量或值,否则会发生编译时错误。类型T必须是可分配的(第5.2节)到方法的声明结果类型,否则会发生编译时错误。
(如果第5.2节声明返回语句 受分配转换的影响,那就太好了。)
答案 1 :(得分:0)
JLS 3#15.12.2.8允许在有限的上下文中进行类型推断。我把它评为设计错误。表达式的含义应该是无上下文的,对每个人来说都更容易。
因为getList()
的含义因其周围环境而异,这对Java程序员来说是违反直觉的(以前从未有过),你会发现前2个编译而第3个编译不令人费解。而且你并不孤单,这种问题一再被提出。他们可以告诉我们RTFS,但是需要阅读规范越多,规范就越差。
当然,如果依赖于上下文的解释确实有用且需要,我们必须是实用的。然而,几乎没有证据支持这一点。这种类型推断是非常危险的,并且使用它的大多数代码都是错误设计的99%。目前还不清楚他们认为有必要添加这种类型的推理规则。
如果Java泛型是“具体化的”,即T
的值可用于运行时的方法调用,我们可以想象这种类型推断是安全且有用的。但是,T
在运行时不可用,因此getList()
调用是无上下文的,因此无法返回调用站点所期望的正确类型。除非有一些外部语言的app逻辑来保护类型的健全性。然后它几乎不是“静态打字”。
有些人走得更远,并要求进行以下类型推断:
Object getFoo(){ .. }
Bar bar = getFoo();
因为“如果我写了那个,当然我知道运行时返回类型是Bar,所以不要再质疑我,愚蠢的编译器!”
我尊重这种观点,但你应该选择一种不同的语言。在静态和动态类型中,程序员都知道类型,但静态类型的要点是,我们希望明确地写下源代码中的类型 - 不是为了帮助编译器,而是为了让自己受益。 “类型推断”违反了确切的目标;只有当它没有为任何阅读类型实际代码的人创造任何神话时,才应该这样做。不幸的是,Java的类型推断非常神秘。