繁体   English   中英

Java中的方法覆盖和重载

[英]Method overriding and overloading in Java

当我遇到一些奇怪的东西时,我正在测试代码,我无法弄清楚为什么会发生这种情况。 所以我将举一个关于那里发生的事情的简单例子。

考虑这些课程

public class A {
    public void print(A a) {
        System.out.println("A");
    }
}

public class B extends A {
    public void print() {
        System.out.println("B");
    }
}

public class C extends B {
    public void print(A a) {
        System.out.println("C");
    }
}

public class E extends C {
    public void print(E e) {
        System.out.println("E");
    }
}

现在在我的main方法中我有这种实例化:

    B b = new E();
    C c = new E();
    E e = new E();

我调用这些方法并获得以下输出。

    b.print(b);
    c.print(e);

OUTPUT:

C
C

我有两个解释,但每个解释都与这些方法调用之一相矛盾。 也有可能我的两个解释都是完全错误的,所以我在这里没有任何要求。

解释1

  • b.print(b)bE一个实例,并b.print(b) B B有一个print方法,它接受类型A的参数但它被E类重载。 然而,该方法的参数是b ,它被提升到B ,因此方法调用匹配C.print(A a)的签名(因为CE的超类),因此结果是合理的。

  • c.print(e) :以与上述相同的方式思考不会在这里解释输出。 cE一个实例,并且被上传到C C有一个print方法,它接受类型A的参数但是它被E类重载。 但在与上述相反的情况下,参数这种方法是e其中的签名相匹配E.print(E e) 所以通过这个推理,输出应该是E而不是!

解释2

在这里,我从第二个方法调用开始,然后推理。

  • c.print(e)cE一个实例,并c.print(e) C C有一个print方法,它接受类型A的参数。 该方法的参数是e ,它是E一个实例,而E又是A的子类。 既然已经upcasted E.print(E e)是从隐藏c 因此,方法调用匹配C.print(A a)的签名,并且该逻辑的输出是合理的。

  • b.print(b)bE一个实例,并b.print(b) B B有一个print方法,它接受类型A的参数(同样它是隐藏的)。 因此,方法调用匹配A.print(A a)的签名,并且通过该逻辑,输出应该是A,而不是。

我真的很困惑这里发生了什么。 请有人解释一下。

将调用哪种方法分两步:

  • 在编译时,根据声明的实例类型决定将使用哪个重载方法
  • 在运行时,根据实际实例类型( 多态 )确定将使用哪些重写方法

    1. b.print(b); // B b = new E();

      • 在编译时,由于声明的b类型是B ,因此只能使用print接受B (或其超类( A ))的实例,这意味着: A.print(A a)

      • 在运行时,一旦在上一步中选择了重载方法, bE )的实际类型将用于选择将要使用的print(A a)版本print(A a)C.print(A a) over A.print(A a)

    2. c.print(e); // C c = new E(); E e = new E();

      • 在编译时,声明的c类型是C ,声明的e类型是E因此只能使用这些方法: A.print(A a)C.print(A a)

      • 在运行时, e的实际类型是E因此选择更具体(在类层次结构中更高)版本: C.print(A a)

我并不打算继续使用tieTYT的答案,但我认为查看main方法的字节码可能会有所帮助。 我将你的代码包装在一个名为Foo的外部类中,并使用了javap -classpath . -c -s Foo javap -classpath . -c -s Foo来拆解它。 这是我们得到的:

public static void main(java.lang.String[]);
  Signature: ([Ljava/lang/String;)V
  Code:
     0: new           #2                  // class Foo$E
     3: dup           
     4: invokespecial #3                  // Method Foo$E."<init>":()V
     7: astore_1      
     8: new           #2                  // class Foo$E
    11: dup           
    12: invokespecial #3                  // Method Foo$E."<init>":()V
    15: astore_2      
    16: new           #2                  // class Foo$E
    19: dup           
    20: invokespecial #3                  // Method Foo$E."<init>":()V
    23: astore_3      
    24: aload_1       
    25: aload_1       
    26: invokevirtual #4                  // Method Foo$B.print:(LFoo$A;)V
    29: aload_2       
    30: aload_3       
    31: invokevirtual #5                  // Method Foo$C.print:(LFoo$A;)V
    34: return        

有趣的行是26和31.请注意,在两行中,编译器选择了接受类型A参数的方法,但这似乎违反了我们的直觉。 我们预计第31行的E.print(E) ,但我们没有得到它。

发生这种情况是因为Java编译器在编译时不知道bce变量的实际类型 ; 它只知道他们声明的类型 在这种情况下,您使用类构造函数来创建对象,但想象一下您是否使用了静态工厂方法。 这些变量的类型可能因非常复杂的逻辑而有所不同,可能涉及反射。 在某些情况下,编译器可能能够确定它们的实际类型,但在所有情况下都不可能这样做。 因此,编译器必须根据声明的变量类型而不是实际类型来决定调用哪个方法。

您可能想知道为什么第26行打印“C”,即使字节码表示调用B.print(A) ......等待一分钟, B甚至没有声明print(A)方法; 它从A继承了print(A) 那么为什么第26行的字节码不能说// Method Foo$A.print:(LFoo$A;)V

这就是方法覆盖的用武之地。在运行时,Java解释器将使用对象的实际类型来确定调用哪个版本的print(A) 由于两个对象都是E类型,并且E没有自己的print(A)方法,因此Java最终调用了C.print(A)

我已将您的示例代码修改为如下所示:

package com.sandbox;

public class Sandbox {
    public static void main(String[] args) {
        B b = new E();
        C c = new E();
        E e = new E();
        b.print(b); //C
        c.print(e); //C
        e.print(e); //E
        e.print(b); //C
    }


    public static class A {
        public void print(A a) {
            System.out.println("A");
        }
    }

    public static class B extends A {
        public void print() {   //doesn't override or overload anyone
            System.out.println("B");
        }
    }

    public static class C extends B {
        public void print(A a) {    //overrides "A"
            System.out.println("C");
        }
    }

    public static class E extends C {
        public void print(E e) {    //Overloads A's print
            System.out.println("E");
        }
    }
}

由于E的方法刚重载,您可以像这样重命名:

package com.sandbox;

public class Sandbox {
    public static void main(String[] args) {
        B b = new E();
        C c = new E();
        E e = new E();
        b.print(b); //C
        c.print(e); //C
        e.unrelatedMethod(e); //E
        e.print(b); //C
    }


    public static class A {
        public void print(A a) {
            System.out.println("A");
        }
    }

    public static class B extends A {
        public void print() {   //doesn't override or overload anyone
            System.out.println("B");
        }
    }

    public static class C extends B {
        public void print(A a) {    //overrides "A"
            System.out.println("C");
        }
    }

    public static class E extends C {
        public void unrelatedMethod(E e) {
            System.out.println("E");
        }
    }
}

事情开始变得更有意义了。 我认为你的样本真正令人困惑的是你的方法有相同的名称,但它们的方式并不相同。

如果这说清楚,请告诉我。 那两个样本完全一样,唯一的区别是命名更清晰。

这是相关文档 ,但简短的故事是:

  • 选择正确方法的过程总是由静态(重写)和动态(重载)部分组成; 如果它在重写后停止,它可能会更直观一些;
  • 所以在你的情况下,首先我们有静态分辨率(选择A类的覆盖方法)然后动态部分在运行时选择该方法的最佳重载(E类)。

暂无
暂无

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

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