理解java中的方法重载和重写

理解java中的方法重载和重写,java,Java,我知道方法重载意味着在子类中定义一个与父类同名但参数不同的方法,方法重写意味着在子类中定义一个与父类具有相同签名的方法 然而,我觉得我失去了一些理解。我试过一些例子。我定义了两个类A和B扩展了A。然后我准备了许多示例,其中包含两种方法的不同组合(方法(A)和方法(B))。例如: 例1:A包含方法(A)和方法(B)和B都不包含 例2:A包含方法(A)和B包含方法(B) 等等。可以有16种这样的组合 现在我创建了类B的两个实例,并在第一个实例中调用了各种方法。我也可以创建实例类型、引用类型和方法

我知道方法重载意味着在子类中定义一个与父类同名但参数不同的方法,方法重写意味着在子类中定义一个与父类具有相同签名的方法

然而,我觉得我失去了一些理解。我试过一些例子。我定义了两个类
A
B扩展了A
。然后我准备了许多示例,其中包含两种方法的不同组合(
方法(A)
方法(B)
)。例如:

  • 例1:
    A
    包含
    方法(A)
    方法(B)
    B
    都不包含
  • 例2:
    A
    包含
    方法(A)
    B
    包含
    方法(B)
等等。可以有16种这样的组合

现在我创建了类
B
的两个实例,并在第一个实例中调用了各种方法。我也可以创建实例类型、引用类型和方法调用的各种组合,但这将是准备示例的大量组合

怀疑是我正在努力猜测一些示例的输出结果。或者更具体地说,我没有理解,或者我应该说一组最小的规则,可以规定所有示例中的输出。这种混淆是由于引用类型与它所引用的实例类型不同。所以我没有得到哪个方法获得优先权,一个在引用类型中,一个在实例类型中。在下面的示例中,我没有得到示例3、4和5的输出。这是16种可能的组合中的5种。我相信可能还有一些更令人困惑的组合

下面是一些例子

示例1–向下广播产生编译时错误

class A {
    public void method(B b)
    {
        System.out.println("A's method(B)");
    }
}

class B extends A { 
    public void method(B b)
    {
        System.out.println("B's method(B)");
    }
}
A a = new B();
B b = new B();
a.method(b); //outputs B's method(B)
a.method(a); //compile time error: The method method(B) in the type A 
            //is not applicable for the arguments (A)
示例2–如果需要,可以进行向上投射

class A {
    public void method(A a) 
    {
        System.out.println("A's method(A)");
    }
}

class B extends A {
    public void method(A a) 
    {
        System.out.println("B's method(A)");
    }
}

A a = new B();
B b = new B();
a.method(b);
a.method(a);
输出

B's method(A)  //upcasting happening
B's method(A)
A's method(B)
B's method(A)
B's method(A)
B's method(A)
示例3

class A {
    public void method(A a) 
    {
        System.out.println("A's method(A)");
    }

    public void method(B b)
    {
        System.out.println("A's method(B)");
    }
}

class B extends A {
    public void method(A a) 
    {
        System.out.println("B's method(A)");
    }
}

A a = new B();
B b = new B();
a.method(b);
a.method(a);
输出

B's method(A)  //upcasting happening
B's method(A)
A's method(B)
B's method(A)
B's method(A)
B's method(A)
示例4

class A {
    public void method(A a) 
    {
        System.out.println("A's method(A)");
    }

    public void method(B b)
    {
        System.out.println("A's method(B)");
    }
}

class B extends A {
    public void method(A a) 
    {
        System.out.println("B's method(A)");
    }

    public void method(B b)
    {
        System.out.println("B's method(B)");
    }
}

A a = new B();
B b = new B();
a.method(b);
a.method(a);
输出

B's method(B)
B's method(A)
示例5

class A {
    public void method(A a) 
    {
        System.out.println("A's method(A)");
    }
}

class B extends A {
    public void method(A a) 
    {
        System.out.println("B's method(A)");
    }

    public void method(B b)
    {
        System.out.println("B's method(B)");
    }
}

A a = new B();
B b = new B();
a.method(b);
a.method(a);
输出

B's method(A)  //upcasting happening
B's method(A)
A's method(B)
B's method(A)
B's method(A)
B's method(A)

当重载时,Java总是尝试选择最具体的方法。 因此,它首先尝试从变量类型(
A
B
)解析该方法,然后在实例上调用该方法。这就是为什么在第三个示例中它调用方法
B.method(A)

您可以看到区别,该方法是通过声明的变量类型来解析的,但是该方法是在变量的实例上调用的。因此,即使变量
a
实际上是
B
的实例,它仍然通过
类a
的静态已知方法解析该方法

在您的第五个也是最后一个示例中,这一点更加清楚。您的
类A
只定义了一个方法,该方法将
A
的任何实例作为参数。它不在乎任何子类是否有一个具有更具体参数的方法,例如
method(B)
它仍然调用
method(a)
,因为这是
a
类型的变量
a
的唯一已知方法

我知道方法重载意味着在子类中定义一个与父类同名但参数不同的方法

不对。如果在同一个类中有另一个具有相同名称但不同签名的方法,则会重载该方法

或者更具体地说,我没有理解,或者我应该说一组最小的规则,可以规定所有示例中的输出

Java语言规范的一节描述了编译器和运行时在确定调用哪个方法时应用的规则。与此问题最相关的小节为15.12.1、15.12.2和15.12.4。这个答案本质上是对那里所说的话的简化

编译器和运行时基本上需要决定三件事:

  • 我应该在哪个班级搜索?(编译时)
  • 我应该打哪一个电话?(编译时)
  • 我应该调用哪个实现?(运行时)
  • 步骤1是根据调用方法的对象的编译时类型决定的。要搜索的类是该对象的编译时类型

    第2步是通过查看在第1步中的类中找到的所有匹配重载,并选择最具体的重载来决定的

    步骤3取决于运行时类型

    让我们将这些规则应用于示例3、4和5。在所有三个示例中,要搜索的类都是
    A

    例3
    A
    中声明了
    方法的两个重载<代码>方法(A)
    B

    • a.方法(b)

      方法(A)
      方法(B)
      在这里都适用,但是
      方法(B)
      更具体,因此在步骤2中选择了
      方法(B)

      方法(B)
      B
      中不被覆盖,因此在步骤3中选择
      A
      中的实现

    • a.方法(a)

      只有
      方法(A)
      适用,因此在步骤2选择
      方法(A)
      ,因为编译器不知道
      A
      的运行时类型。请注意,步骤2是在编译时执行的

      方法(A)
      B
      中被覆盖,因此在步骤3中选择
      B
      中的实现

    例4 与例3相同,不同的是
    方法(B)
    B
    中也被覆盖,因此
    a.方法(B)
    调用
    B
    中的实现

    例5 在
    A
    中只声明了
    方法的一个重载<代码>B
    声明2个重载。其中一个覆盖了
    A
    中的
    方法(A)

    与其他示例不同,在解析
    a.method时