参数化方法为什么有时可以隐式绑定,有时不能?

4

最近我重构代码时遇到了这个问题:

下面的"getList()"方法具有参数化返回类型。在此之后,我放置了三个尝试将<T>隐式绑定到<Integer>的方法。

我无法弄清楚的是,为什么前两个编译并且正确运行,而第三个(bindViaMethodInvocation)甚至无法编译。

有任何线索吗?

在StackOverflow上寻找类似的问题时,我找到了这个问题:Inferred wildcard generics in return type。那里的答案(由Laurence Gonsalves提供)有一些有用的参考链接来解释应该发生的事情: "问题在于(就像您所提供的那样),编译器正在执行Capture Conversion。我相信这是由于JLS的§15.12.2.6所致。"

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;
    }
}

谢谢Matt!我猜我在那些地方没有正确使用Markdown。 - Martin Snyder
2个回答

7
前两种方法使用getList()在上下文中进行赋值转换 - 要么是分配给List<Integer>,要么是从返回List<Integer>的方法中。对于bindViaMethodInvocation,情况不是如此 - 使用表达式作为方法参数受赋值转换的影响。
来自JLS第15.12.2.8节

如果任何方法的类型参数没有从实际参数的类型中推断出,则现在将它们推断如下。

  • 如果方法结果出现在将被分配转换(§5.2)到类型S的上下文中,则让R成为方法的声明结果类型,并且让R'= R[T1=B(T1)...Tn=B(Tn)],其中B(Ti)是在前一节中推断的Ti的类型,或者如果没有推断出类型,则为Ti。
JLS没有很清楚地解释为什么return语句在这里起作用。我能找到的最接近的是在14.17中:

带有Expression的返回语句必须包含在声明将返回值(§8.4)的方法声明中,否则会出现编译时错误。Expression必须表示某种类型T的变量或值,否则会出现编译时错误。类型T必须可以分配(§5.2)到方法的声明结果类型,否则会出现编译时错误。

(如果第5.2节说明return语句受到赋值转换的影响,那就太好了。)

0
JLS 3 #15.12.2.8 允许在有限的上下文中进行类型推断。我认为这是一个设计错误。表达式的含义应该是无上下文的,这样对于每个人来说都更容易。
因为 getList() 的含义取决于其周围的环境,这与 Java 程序员的直觉相反(以前从未遇到过),你会发现前两个编译通过,而第三个则不通过,这让你感到困惑。你并不孤单,这种类型的问题已经被反复提出。他们可以告诉我们去读规范,但是需要阅读规范的次数越多,规范就越糟糕。
当然,如果上下文相关的解释确实有用且必要,我们必须实际操作。然而,很少有证据支持这一点。这种类型推断是非常危险的,大多数使用它的代码都是 99% 错误的设计。不清楚他们当时的想法是什么,以至于他们认为有必要添加这种类型推断规则。
如果 Java 泛型是“具体化”的,即在运行时方法调用中可用 T 的值,我们可以想象这种类型推断是安全和有用的。但是,在运行时不可用 T,因此 getList() 调用是上下文无关的,无法返回调用方期望的正确类型。除非有一些超语言应用逻辑保护类型的完整性。那么它几乎不是“静态类型”。
有些人进一步要求以下类型推断:
Object getFoo(){ .. }

Bar bar = getFoo(); 

因为“如果我写了那个,当然我知道运行时返回类型是Bar,所以别问我,愚蠢的编译器!”

我尊重这种观点,但你应该选择一种不同的语言。在静态和动态类型中,程序员都知道类型,但静态类型的重点是我们想要明确地在源代码中写下类型 - 不是为了帮助编译器,而是为了使自己受益。 “类型推断”违反了这个确切的目标;只有在不会给任何阅读代码的人造成类型混淆的情况下才应该执行它。不幸的是,Java的类型推断非常神秘。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接