繁体   English   中英

Java 方法重载 - 同一继承树中的通用参数和参数

[英]Java method overloading - Generic parameter & parameters within same inheritance tree

假设我有以下代码:

// 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 编程语言使用选择最具体的方法的规则。

...

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

  • m 2是通用的,对于参数表达式e 1 , ..., e k ,第 18.5.4 节推断m 1m 2更具体。

  • m 2不是通用的,并且m 1m 2通过严格或松散调用适用,并且其中m 1具有形式参数类型 S1, ..., Sn 并且m 2具有形式参数类型 T 1 , ..., T n ,对于所有i (1 ≤ in , n = k ) 的参数e i的类型 S i比 T i更具体。

...

对于任何表达式,如果 S <: T (§4.10),则类型 S 比类型 T更具体

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

本节将详细介绍与第二个方法相关的第一个条件是通用的。 这有点冗长,但我认为重要的部分是:

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

...

m 1为第一种方法, m 2为第二种方法。 其中m 2具有类型参数 P 1 , ..., P p ,令 α 1 , ..., α p为推理变量,并令 θ 为替代 [P 1 :=α 1 , ..., P p :=α p ]。

...

确定m 1是否比m 2更具体的过程如下:

...

如果T i是一个适当的类型,若S i大于T更具体为E I(§15.12.2.5),否则为假的结果为 (请注意, S i始终是正确类型。)

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

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

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

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

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

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

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

Object foo(Object);

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

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

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

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

所以,这很简单:

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM