【问题标题】:Java method overloading - Generic parameter & parameters within same inheritance treeJava方法重载-同一继承树中的通用参数和参数
【发布时间】:2016-03-10 18:21:41
【问题描述】:

假设我有以下代码:

// Method acception generic parameter
public static <T> T foo(T para) {
    return para;
}

// Method accepting Integer parameter
public static Integer foo(Integer para) {
    return para + 1;
}

// Method accepting Number parameter
public static Number foo(Number para) {
    return para.intValue() + 2;
}

public static void main(String[] args) {
    Float f = new Float(1.0f);
    Integer i = new Integer(1);
    Number n = new Integer(1);
    String s = "Test";

    Number fooedFloat = foo(f);     // Uses foo(Number para)
    Number fooedInteger = foo(i);   // Uses foo(Integer para)
    Number fooedNumber = foo(n);    // Uses foo(Number para)
    String fooedString = foo(s);    // Uses foo(T para)

    System.out.println("foo(f): " + fooedFloat);
    System.out.println("foo(i): " + fooedInteger);
    System.out.println("foo(n): " + fooedNumber);
    System.out.println("foo(s): " + fooedString);
}

输出如下所示:

foo(f): 3
foo(i): 2
foo(n): 3
foo(s): Test

现在的问题:

  1. foo(n) 调用foo(Number para),很可能是因为n 被定义为Number,即使它分配了Integer。那么,在没有动态绑定的情况下,我是否正确地假设采用哪些重载方法的决定发生在编译时? (关于静态动态绑定的问题)
  2. foo(f) 使用 foo(Number para),而 foo(i) 使用 foo(Integer para)。只有foo(s) 使用通用版本。因此,编译器总是查看给定类型是否存在非泛型实现,并且只有在没有时才回退到泛型版本? (关于泛型的问题)
  3. 同样,foo(f) 使用foo(Number para),而foo(i) 使用foo(Integer para)。然而,Integer i 也将是 Number。所以总是采用继承树中“最外层”类型的方法吗? (关于继承的问题)

我知道这些问题很多,并且该示例并非来自生产代码,但我只想知道“发生了什么”以及为什么会发生。

非常感谢任何指向 Java 文档或 Java 规范的链接,我自己找不到它们。

【问题讨论】:

    标签: java generics inheritance overloading


    【解决方案1】:

    确定在编译时调用哪个方法签名的规则在language specification 中进行了解释。特别重要的是关于选择the most specific method 的部分。以下是与您的问题相关的部分:

    如果多个成员方法既可访问又适用于方法调用,则有必要选择一个为运行时方法分派提供描述符。 Java 编程语言使用选择最具体方法的规则。

    ...

    一个适用的方法m<sub>1</sub>比另一个适用的方法m<sub>2</sub>更具体,用于使用参数表达式e<sub>1</sub>、...、e<sub>k</sub>的调用,如果以下任何一项为真:

    • m<sub>2</sub> 是通用的,m<sub>1</sub> 被推断为比 m<sub>2</sub> 更具体的参数表达式 e<sub>1</sub>, ..., e<sub>k</sub> §18.5.4。

    • m<sub>2</sub> 不是泛型,m<sub>1</sub>m<sub>2</sub> 可通过严格或松散调用适用,其中m<sub>1</sub> 具有形参类型 S1、...、Sn 和 m<sub>2</sub> 具有形参类型T1, ..., Tn,类型 Si 比 Ti 更具体e<sub>i</sub> 对于所有 i (1 ≤ in, n = k)。

    ...

    对于任何表达式,如果 S <: t s>比类型 T 更具体。

    在这种情况下,IntegerNumber 更具体,因为Integer 扩展了Number,所以每当编译器检测到对foo 的调用时,该调用采用声明的变量输入Integer,它将为foo(Integer)添加一个调用。

    this section 中解释了与第二个通用方法相关的第一个条件的更多信息。这有点冗长,但我认为重要的部分是:

    当测试一种适用的方法比另一种更具体时(§15.12.2.5),其中第二种方法是通用的,有必要测试第二种方法的类型参数的某些实例化是否可以被推断为使第一种方法比第二种方法更具体。

    ...

    m<sub>1</sub> 成为第一种方法,m<sub>2</sub> 成为第二种方法。其中m<sub>2</sub>有类型参数P1, ..., Pp, let α1, ..., α p 为推理变量,设 θ 为代入 [P1:=α1, ..., Pp :=αp].

    ...

    判断m<sub>1</sub>是否比m<sub>2</sub>更具体的过程如下:

    ...

    如果 Ti 是正确的类型,如果 Si 比 Ti 更具体,则结果为 true sub> 为 ei (§15.12.2.5),否则为 false。 (请注意,Si 始终是正确的类型。)

    这基本上意味着foo(Number)foo(Integer) 都比foo(T) 更具体,因为编译器可以为生成foo(Number) 和@987654356 的泛型方法(例如Number 本身)推断出至少一种类型@ 更具体(这是因为Integer &lt;: NumberNumber &lt;: Number)。

    这也意味着在您的代码中,foo(T) 仅适用于(并且本质上是最具体的方法,因为它是唯一适用的方法)用于传递 String 的调用。

    【讨论】:

      【解决方案2】:

      在没有动态绑定的情况下,我是否正确地假设,采用哪些重载方法的决定发生在编译时?

      是的,Java 在编译时根据参数的声明类型,从目标对象的声明类型提供的备选方案中选择方法的可用重载。

      动态绑定适用于根据调用目标的运行时类型在具有相同签名的方法中进行选择。它与实际参数的运行时类型没有直接关系。

      所以编译器总是查看给定类型是否存在非泛型实现,只有在没有时才回退到泛型版本?

      由于类型擦除,您的泛型方法的实际签名是

      Object foo(Object);
      

      在您测试的参数类型中,这是重载选项中仅针对 String 的最佳匹配。

      所以总是采用继承树中“最外层”类型的方法吗?

      或多或少。在重载中进行选择时,编译器会选择与声明的参数类型最匹配的替代方案。对于引用类型的单个参数,这是其参数类型是参数的声明类型或其最近的超类型的方法。

      如果 Java 必须在多参数方法的重载中进行选择,并且它没有完全匹配,事情可能会变得很冒险。当有原始参数时,它还必须考虑允许的参数转换。完整的细节占据了 JLS 的大部分内容。

      【讨论】:

        【解决方案3】:

        所以,这很简单:

        1) 是的,决定是在编译时做出的。编译器选择具有最具体匹配类型的方法。因此,当您作为参数传递的变量被声明为Number 时,编译器将选择Number 版本,即使它在运行时是Integer。 (如果编译器发现两个“相等匹配”的方法,一个模棱两可的方法错误会导致编译失败)

        2) 在运行时,没有泛型,一切都只是一个Object。泛型仅是编译时和源代码功能。因此编译器必须尽其所能,因为VM肯定做不到。

        【讨论】:

          猜你喜欢
          • 2015-11-07
          • 2014-09-13
          • 1970-01-01
          • 2023-02-09
          • 1970-01-01
          • 1970-01-01
          • 2017-07-08
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多