代码之家  ›  专栏  ›  技术社区  ›  picheto

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

  •  0
  • picheto  · 技术社区  · 9 年前

    我用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.");
        }
    }
    

    正如我所期待的, 以下代码调用 介绍自己() 每个运行时对象的方法 ,尽管变量被声明为基 Bicycle 班如果我要将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.  
         }
    }
    

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

    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. 而不是类的运行时对象已知的最合适的(通过签名) D ? (另外,我认为有一个自动转换,因为参数类型不同。)为什么它的行为看起来与Bicycle示例如此不同?谢谢

    3 回复  |  直到 9 年前
        1
  •  1
  •   user3758745    9 年前

    只有当你重写时,多态性才起作用。这里您重载了不同的方法,因此当您声明时:

     A a1 = D();  
    

    记住,父类不知道子类的方法,但子类知道父类的方法。所以这里你可以用D代替A,但你不能调用D的方法。对不起,如果我的英语很糟糕,但TLDR:A只知道1个方法calc(double-num),因为double-num也可以接受int和long,这就是函数工作的原因。否则就行不通了。

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

     Bicycle bike = new CityBike();
     bike.introduceYourSelf("I'm a city bike"); //error - Bicycle does not have method with argument string
    
        2
  •  0
  •   Jean-François Savard    9 年前

    为什么a1(具有声明的类型A和 运行时类型D)调用A中声明的方法,而不是 最合适的。。。

    实际上,这不是最合适的。

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

    可能的转换为:

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

    • short到int、long、float或double

    • char到int、long、float或double

    • int到long、float或double

    • 长到浮动或双倍

    • 浮动到双倍

    您希望选择将浮点作为参数的方法。然而,正如你在上面的列表中看到的,double永远不可能适合另一种类型…double。

    JLS 15.12.2.5 结束询问:

    非正式的直觉是,有一种方法比 另一个如果 可以传递第一个方法处理的任何调用 而不会出现编译时错误。

    因此,由于float可以传递给double,而double不能传递给float,所以选择A中的方法是最合适的。


    为什么它的表现与自行车的表现如此不同?

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

        3
  •  0
  •   Hengqi Chen    9 年前

    B、C、D中的calc方法不会覆盖A中的calc-mehtod,因此当您通过a1(它是A)调用calc时,它实际上调用了A中的方法define(public int calc(double num)),因此结果是11。(也就是说,a1被视为A,而不是D。请注意,A中只有一个方法define)

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