为什么参数化方法在某些情况下可以隐式绑定而不是其他情况?

时间:2011-04-05 14:23:40

标签: java generics

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

下面的方法“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;
    }
}

2 个答案:

答案 0 :(得分:7)

前两个方法在受赋值转换约束的上下文中使用getList() - 对List<Integer>的赋值或返回List<Integer>的方法的return语句。对于bindViaMethodInvocation不是为真 - 使用表达式作为方法参数 不受赋值转换的影响。

来自JLS section 15.12.2.8

  

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

     
      
  • 如果方法结果发生在将要进行赋值转换(第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的类型推断非常神秘。