Java 中的继承和重载
Inheritance and overloading in Java
我正在备考,我需要一些帮助来理解以下代码片段中发生的事情。
class A {
public void method1(A X) {
System.out.println("A");
}
}
class B extends A {
public void method2() {
System.out.println("B");
}
}
class C extends B {
public void method1(A x) {
System.out.println("C");
}
}
class D extends C {
public void method1(D x) {
System.out.println("D");
}
}
public class test {
public static void main(String[] args) {
C c = new D();
B b = c;
c.method1(c); // prints C
b.method1(b); // prints C
}
}
好的,这就是我的想法:c.method1(c) 调用 C 中的 method1 而不是 D 中的 method1,因为 c 被清除为 C,因此 D 中的 method1 不可见。但是 b.method1(b) 比较难。 B 没有 method1,我假设将使用超类中的 method1,但事实并非如此。为什么要使用C中的方法?我在 b 中放入了一个新的 D,但是 D 的特化是不可见的,因为 b 来自类型 B。
引用实例的变量类型与调用的方法无关。
对象的类型定义了调用哪个方法; subclasses overriding 方法来改变实例的 behaviour,同时仍然保持它们对 super 类型变量的可分配性class。这就是 OO 的本质 - 请参阅 Liskov substitution principle.
然而,当涉及到重载方法时,持有参数的 varialbe 的类型控制着调用哪个方法——它所引用的实例的类型是无关紧要的。
你的方法覆盖被破坏了。
Class C
中的方法等同于 Class D
中的方法是不同的。您没有正确输入。签名是不同的。
您可以通过在您的方法上写 @Override
来测试它。
@Override
public void method1(D x) {
System.out.println("D");
}
由于您在子类中更改了该方法的签名,因此上述代码不会编译并给您一个错误。
让我们看看您的示例代码并评论调用了哪个方法,为什么调用它以及为什么这里的所有内容都不是继承的。
class A {
public void method1(A X) {
System.out.println("A"); <--|
} |
} |
class B extends A { |
public void method2() { | This is the only overriding happening
System.out.println("B"); | because they share the signature method1(A)
} | The other signatures are method1() from class B
} | and method1(D) from class D
class C extends B { |
public void method1(A x) { |
System.out.println("C"); |
} -------
}
class D extends C {
public void method1(D x) {
System.out.println("D");
}
}
public class test {
public static void main(String[] args) {
C c = new D();
B b = c;
c.method1(c); // prints C
b.method1(b); // prints C
}
}
由于您是从定义为 C
的对象调用方法 1,因此您将获得结果 C。
除了 Object
class 的方法之外,此 class 已知的唯一方法是 method1(A) 和 method1()。由于您的 Class
beeing 定义为 C 它将正确调用 class C
中的方法,因为 method1 函数
来自 class D
不会覆盖来自 class C
.
的那个
编辑以回答您的评论:
此时您正在调用带有签名 method1(A)
的方法。因为你的 Object
实际上代表了 class D
它会注意到方法 method1(A)
被以前继承的 class C
和因此将打印 C
。简而言之,class 的定义定义了允许调用哪些方法(在对象的可见范围内),但它引用的对象的实际类型定义了如何调用方法(如果它在某些时候被覆盖了)。
如果我们看到 class C
中的代码方法 method1
正在被 B 覆盖,B 最终继承自 class A
。由于覆盖是运行时多态性,尽管您已经创建了 class D 的实例,但是由于这两种方法不同,它调用 class C method1
如果你想调用D
的方法,你需要将方法的参数更改为A
总而言之,每个方法在每个继承级别的可见性如下:
class D:
public void method1(D x) { System.out.println("D"); }
public void method1(A x) { System.out.println("C"); }
public void method2() { System.out.println("B"); }
class C:
public void method1(A x) { System.out.println("C"); }
public void method2() { System.out.println("B"); }
class乙:
public void method1(A x) { System.out.println("A"); }
public void method2() { System.out.println("B"); }
class答:
public void method1(A x) { System.out.println("A"); }
同样重要的是要指出您示例中的变量 'c' 和 'b' 是同一个对象,并且是 D
.
的实例
所以...如果你调用c.method1(c);
,它会打印"C",因为D是A的一个实例(它实际上是一个D,但它也是一个继承的A),所以你可以调用 method1(A)
,对于 D,打印 "C"。 (那是满嘴)。为什么它不打印 "D" 你会问?因为变量声明为C
,编译器只能link到method1(A)
.
如果您调用 b.method1(b);
,它会打印 "C",因为您的变量 b
实际上是 D
的实例,因为您将其创建为 new D()
].实际上 c
和 b
指向相同类型的对象 D
.
调用方法时,JVM 在这种情况下会查看对象的实际类型 D
,而不是它声明为 B
.
的类型
一个很好的记忆方法是当你有类似
的东西时
B b = new D()
等式的左边部分主要由编译器使用。请记住,method1(A)
和 method1(D)
是两种不同的方法(因为不完全相同的签名,不同的参数类型)。
JVM 在运行时使用等式的右边部分。它定义了该变量 b
.
在运行时的实际类型
调用对象方法的原理很简单。系统会从对象的class of instantiation
开始查找方法,然后他会向上找它的直母,直到找到为止。例如 :
如果你用 D class
实例化,假设 method1 只存在于 A class
中。
所以系统将完成这个研究步骤:
D(not found) > C(not found) > B(not found) > A(found)
所以他会使用 A class method
如果我没猜错,你的问题是,为什么你打印的是 "C",而不是 b.method1(b)
的 "A",如你所料。
答案很简单,你在这里处理的是动态分派(动态绑定)和静态分派(静态绑定)的组合。第一个是基于运行时类型完成的,第二个是基于编译时类型的。
注意,顺便说一句,在 D 中你有 两个 方法,具有不同的签名(记住,要覆盖,你需要签名的精确匹配,所以在这种情况下它是重载 == 编译时多态性):
public void method1(D x)
public void method1(A x)
那么,当您执行 b.method1(b)
.
时,这里发生了什么
您需要先确定要调用的方法签名,然后再找到匹配的方法。第一个是基于编译时类型。您传递的参数是 B 类型(隐式向上转换),因此它选择 method1(A x)
(method1(D x)
具有不适合的签名)并将向上搜索 method1(A x)
继承树以找到比赛。第一场比赛在 C 中。
有关详细信息,请查看此处:JLS,尤其是。解释:
When a method is invoked (§15.12), the number of actual arguments (and
any explicit type arguments) and the compile-time types of the
arguments are used, at compile time, to determine the signature of the
method that will be invoked (§15.12.2)
我正在备考,我需要一些帮助来理解以下代码片段中发生的事情。
class A {
public void method1(A X) {
System.out.println("A");
}
}
class B extends A {
public void method2() {
System.out.println("B");
}
}
class C extends B {
public void method1(A x) {
System.out.println("C");
}
}
class D extends C {
public void method1(D x) {
System.out.println("D");
}
}
public class test {
public static void main(String[] args) {
C c = new D();
B b = c;
c.method1(c); // prints C
b.method1(b); // prints C
}
}
好的,这就是我的想法:c.method1(c) 调用 C 中的 method1 而不是 D 中的 method1,因为 c 被清除为 C,因此 D 中的 method1 不可见。但是 b.method1(b) 比较难。 B 没有 method1,我假设将使用超类中的 method1,但事实并非如此。为什么要使用C中的方法?我在 b 中放入了一个新的 D,但是 D 的特化是不可见的,因为 b 来自类型 B。
引用实例的变量类型与调用的方法无关。
对象的类型定义了调用哪个方法; subclasses overriding 方法来改变实例的 behaviour,同时仍然保持它们对 super 类型变量的可分配性class。这就是 OO 的本质 - 请参阅 Liskov substitution principle.
然而,当涉及到重载方法时,持有参数的 varialbe 的类型控制着调用哪个方法——它所引用的实例的类型是无关紧要的。
你的方法覆盖被破坏了。
Class C
中的方法等同于 Class D
中的方法是不同的。您没有正确输入。签名是不同的。
您可以通过在您的方法上写 @Override
来测试它。
@Override
public void method1(D x) {
System.out.println("D");
}
由于您在子类中更改了该方法的签名,因此上述代码不会编译并给您一个错误。
让我们看看您的示例代码并评论调用了哪个方法,为什么调用它以及为什么这里的所有内容都不是继承的。
class A {
public void method1(A X) {
System.out.println("A"); <--|
} |
} |
class B extends A { |
public void method2() { | This is the only overriding happening
System.out.println("B"); | because they share the signature method1(A)
} | The other signatures are method1() from class B
} | and method1(D) from class D
class C extends B { |
public void method1(A x) { |
System.out.println("C"); |
} -------
}
class D extends C {
public void method1(D x) {
System.out.println("D");
}
}
public class test {
public static void main(String[] args) {
C c = new D();
B b = c;
c.method1(c); // prints C
b.method1(b); // prints C
}
}
由于您是从定义为 C
的对象调用方法 1,因此您将获得结果 C。
除了 Object
class 的方法之外,此 class 已知的唯一方法是 method1(A) 和 method1()。由于您的 Class
beeing 定义为 C 它将正确调用 class C
中的方法,因为 method1 函数
来自 class D
不会覆盖来自 class C
.
编辑以回答您的评论:
此时您正在调用带有签名 method1(A)
的方法。因为你的 Object
实际上代表了 class D
它会注意到方法 method1(A)
被以前继承的 class C
和因此将打印 C
。简而言之,class 的定义定义了允许调用哪些方法(在对象的可见范围内),但它引用的对象的实际类型定义了如何调用方法(如果它在某些时候被覆盖了)。
如果我们看到 class C
中的代码方法 method1
正在被 B 覆盖,B 最终继承自 class A
。由于覆盖是运行时多态性,尽管您已经创建了 class D 的实例,但是由于这两种方法不同,它调用 class C method1
如果你想调用D
的方法,你需要将方法的参数更改为A
总而言之,每个方法在每个继承级别的可见性如下:
class D:
public void method1(D x) { System.out.println("D"); }
public void method1(A x) { System.out.println("C"); }
public void method2() { System.out.println("B"); }
class C:
public void method1(A x) { System.out.println("C"); }
public void method2() { System.out.println("B"); }
class乙:
public void method1(A x) { System.out.println("A"); }
public void method2() { System.out.println("B"); }
class答:
public void method1(A x) { System.out.println("A"); }
同样重要的是要指出您示例中的变量 'c' 和 'b' 是同一个对象,并且是 D
.
所以...如果你调用c.method1(c);
,它会打印"C",因为D是A的一个实例(它实际上是一个D,但它也是一个继承的A),所以你可以调用 method1(A)
,对于 D,打印 "C"。 (那是满嘴)。为什么它不打印 "D" 你会问?因为变量声明为C
,编译器只能link到method1(A)
.
如果您调用 b.method1(b);
,它会打印 "C",因为您的变量 b
实际上是 D
的实例,因为您将其创建为 new D()
].实际上 c
和 b
指向相同类型的对象 D
.
调用方法时,JVM 在这种情况下会查看对象的实际类型 D
,而不是它声明为 B
.
一个很好的记忆方法是当你有类似
的东西时B b = new D()
等式的左边部分主要由编译器使用。请记住,method1(A)
和 method1(D)
是两种不同的方法(因为不完全相同的签名,不同的参数类型)。
JVM 在运行时使用等式的右边部分。它定义了该变量 b
.
调用对象方法的原理很简单。系统会从对象的class of instantiation
开始查找方法,然后他会向上找它的直母,直到找到为止。例如 :
如果你用 D class
实例化,假设 method1 只存在于 A class
中。
所以系统将完成这个研究步骤:
D(not found) > C(not found) > B(not found) > A(found)
所以他会使用 A class method
如果我没猜错,你的问题是,为什么你打印的是 "C",而不是 b.method1(b)
的 "A",如你所料。
答案很简单,你在这里处理的是动态分派(动态绑定)和静态分派(静态绑定)的组合。第一个是基于运行时类型完成的,第二个是基于编译时类型的。
注意,顺便说一句,在 D 中你有 两个 方法,具有不同的签名(记住,要覆盖,你需要签名的精确匹配,所以在这种情况下它是重载 == 编译时多态性):
public void method1(D x)
public void method1(A x)
那么,当您执行 b.method1(b)
.
您需要先确定要调用的方法签名,然后再找到匹配的方法。第一个是基于编译时类型。您传递的参数是 B 类型(隐式向上转换),因此它选择 method1(A x)
(method1(D x)
具有不适合的签名)并将向上搜索 method1(A x)
继承树以找到比赛。第一场比赛在 C 中。
有关详细信息,请查看此处:JLS,尤其是。解释:
When a method is invoked (§15.12), the number of actual arguments (and any explicit type arguments) and the compile-time types of the arguments are used, at compile time, to determine the signature of the method that will be invoked (§15.12.2)