编译时行为与 运行 时多态性(或方法签名)

Behaviour of compile-time vs run-time polymorphism (or method signature)

我用 Java 编写了以下说明性代码。它显示了对不同自行车的 introduceYourself() 方法的覆盖。

public class Bicycle{
    public void introduceYourself(){
        System.out.println("Hello I am just a bicycle.");
    }
}

public class MountainBike extends Bicycle{
    public void introduceYourself(){
        System.out.println("Hello I am a mountain bike and I love going outdoors.");
    }
}

public class CityBike extends Bicycle{
    public void introduceYourself(){
        System.out.println("My name is city bike and I prefer calm trips.");
    }
}

正如我所料,以下代码为每个 运行 时间对象 调用了 introduceYourself() 方法,尽管变量被声明为基地 Bicycle class。如果我要将 Bicycle 或 Bicycle 子类型对象添加到数组并循环调用该方法,这将很有用。

public class HelloWorld{

     public static void main(String []args){
        Bicycle b1 = new Bicycle();
        Bicycle b2 = new MountainBike();
        Bicycle b3 = new CityBike();

        b1.introduceYourself(); // Output: Hello I am just a bicycle.       
        b2.introduceYourself(); // Output: Hello I am a mountain bike and I love going outdoors.
        b3.introduceYourself(); // Output: My name is city bike and I prefer calm trips.  
     }
}

但是,我无法理解其他代码的行为。我有以下 classes,它们再次显示继承,但方法具有不同的签名(重载):

public class A{
    public int calc (double num){
    return (int)(num + 1);
    }
}

public class B extends A{
    public int calc (long num){
        return (int)(num + 2);
    }
}

public class C extends B{
    public int calc (int num){
        return (num + 3);
    }
}

public class D extends C{
    public int calc (float num){
        return (int)(num + 4);
    }
}

并且main方法中的代码如下:

public class HelloWorld{
 public static void main(String []args){
    int num1 = 10;
    long num2 = 10;

    A a1 = new D();
    D d1 = new D();

    System.out.println(a1.calc(num1)); // Output: 11
    System.out.println(a1.calc(num2)); // Output: 11

    System.out.println(d1.calc(num1)); // Output: 13
    System.out.println(d1.calc(num2)); // Output: 12
 }

}

为什么a1引用的对象(声明类型A和运行时间类型D)调用A中声明的方法而不是由其 class D 的 运行time 对象所知的最合适的(通过签名)? (另外,我想有一个自动转换,因为参数类型不一样。)为什么它的行为看起来与 Bicycle 示例如此不同?谢谢。

B、C、D中的calc方法不会覆盖A中的calc方法,所以当你通过a1(它是一个A)调用calc时,它实际上调用了A中定义的方法(public int calc(double num)), 所以结果是11.(也就是说a1被当做A, 不是D. 注意A里只有一个方法定义)

但是当你通过 d1 调用 calc 时,它定义了 4 个版本的 calc,结果取决于你的参数类型和参数。

Why does the object referenced by a1 (with declared type A and run-time type D) call the method declared in A instead of the most-appropiate one...

其实这不是最合适的

考虑 Widening primitive conversions 将用于选择最合适的方法。

可能的转换是:

  • byte 到 short、int、long、float 或 double

  • short 到 int、long、float 或 double

  • char 到 int、long、float 或 double

  • int 到 long、float 或 double

  • long to float or double

  • 浮动到两倍

您希望选择以浮点数为参数的方法。但是,正如您在上面的列表中看到的那样,double 永远不能适合其他类型... double.

JLS 15.12.2.5 关闭审讯:

The informal intuition is that one method is more specific than another if any invocation handled by the first method could be passed on to the other one without a compile-time error.

因此float可以传给double,double不能传给float,所以选择A的方法最合适。


Why does it seem to behave so differently from the Bicycle example?

在自行车示例中,您重写了方法,而在第二个示例中您重载了方法。重载最合适的方法时(根据 JLS 15.12.2.5),而重写时您将调用运行时对象的 "nearest" 方法。

多态性仅在您覆盖时才会接管。在这里你重载了不同的方法,所以当你声明时:

 A a1 = D();  

记住,父class对子class的方法一无所知,但子class知道父class的方法。所以在这里你可以用 D 代替 A,但是你不能调用 D 的方法。对不起,如果我的英语很烂,但是 TLDR:A 只知道 1 种方法 calc(double num) 并且因为 double num 也可以接受 int 和 long,这就是该函数起作用的原因。否则它不会工作。

假设在第一个示例中,您在 class CityBike 中有一个 introduceYourSelf(String name) 方法,您可以这样做:

 Bicycle bike = new CityBike();
 bike.introduceYourSelf("I'm a city bike"); //error - Bicycle does not have method with argument string