让你怀疑人生的重载和重写的区别

开发 前端
如果你认为你对java的重载和重写已经很了解了,那么我想通过下面的例子你可能会感到怀疑人生了。如果你能完全回答对下面的题目,那我觉得你真的非常非常牛X了。

如果你认为你对java的重载和重写已经很了解了,那么我想通过下面的例子你可能会感到怀疑人生了。如果你能完全回答对下面的题目,那我觉得你真的非常非常牛X了。

[[349661]]

单一调度

  1. class Parent { 
  2.   void print(String a) { log.info("Parent - String"); } 
  3.   void print(Object a) { log.info("Parent - Object"); } 
  4.   
  5. class Child extends Parent { 
  6.   void print(String a) { log.info("Child - String"); } 
  7.   void print(Object a) { log.info("Child - Object"); } 

下面将会打印什么?

  1. String string = ""
  2. Object stringstringObject = string; 
  3.   
  4. // 打印什么? 
  5. Child child = new Child(); 
  6. child.print(string); 
  7. child.print(stringObject); 
  8.   
  9. Parent parent = new Child(); 
  10. parent.print(string); 
  11. parent.print(stringObject); 

答案:

  1. child.print(string);        // 打印: "Child - String" 
  2. child.print(stringObject);  // 打印: "Child - Object" 
  3.   
  4. parent.print(string);       // 打印: "Child - String" 
  5. parent.print(stringObject); // 打印: "Child - Object" 

print(string)和 parent.print(string)是 Java 面向对象程序设计的教科书示例。被调用的方法取决于实际的实例类型,而不是声明的实例类型。例如,无论你将变量定义为 Child 还是 Parent,因为实际的实例类型是 Child,都将调用 Child: : print。

第二组则更为复杂,因为都是完全相同的字符串。唯一的区别是字符串被声明为 String,而 stringObject 被声明为 Object。在处理方法参数时,重要的是参数的声明类型,而不是它的实际类型。即使实际参数类型是 String,也会调用 print (Object)

隐式重写

  1. class Parent { 
  2.   void print(Object a) { log.info("Parent - Object"); } 
  3.   
  4. class Child extends Parent { 
  5.   void print(String a) { log.info("Child - String"); } 

打印什么?

  1. String string = ""
  2. Parent parent = new Child(); 
  3. parent.print(string); 

答案:

  1. parent.print(string);  // 打印: "Parent - Object" 

实际的实例类型是 Child,声明的参数类型是 String,我们确实有一个为 Child: : print (String)定义的方法。实际上,这正是在前一个示例中调用 parent.print (string)时选择的内容。但是,这并不是在这里调用的方法。

在检查子类重写之前,Java 似乎首先选择要调用哪个方法。在这种情况下,声明的实例类型是 Parent,Parent 中唯一匹配的方法是 Parent: : print (Object)。然后,当 Java 检查 Parent: : print (Object)的任何潜在重写时,它没有找到任何重写,因此这就是执行的方法。

显式重写

  1. class Parent { 
  2.   void print(Object a) { log.info("Parent - Object!"); } 
  3.   void print(String a) { throw new RuntimeException(); } 
  4.   
  5. class Child extends Parent { 
  6.   void print(String a) { log.info("Child - String!"); } 

打印什么?

  1. String string = ""
  2. Parent parent = new Child(); 
  3. parent.print(string); 

答案:

  1. parent.print(string);  // 打印: "Child - String!" 

这个示例与前面的示例之间的唯一区别是,我们添加了一个新的 Parent: : print (String)方法。这个方法实际上从来没有被执行过——如果它运行了,它会抛出一个异常!然而,它的存在使 Java 执行了一个不同的方法。

在计算 Parent.print (String)时,运行时现在找到一个匹配的 Parent: : print (String)方法,然后看到这个方法被 Child: : print (String)重写。

模糊参数

  1. class Foo { 
  2.   void print(Cloneable a) { log.info("I am cloneable!"); } 
  3.   void print(Map a) { log.info("I am Map!"); } 

下面打印的是什么?

  1. HashMap cloneableMap = new HashMap(); 
  2. Cloneable cloneable = cloneableMap
  3. Map map = cloneableMap
  4.   
  5. // What gets printed? 
  6. Foo foo = new Foo(); 
  7. foo.print(map); 
  8. foo.print(cloneable); 
  9. foo.print(cloneableMap); 

答案:

  1. foo.print(map);           // 打印: "I am Map!" 
  2. foo.print(cloneable);     // 打印: "I am cloneable!" 
  3. foo.print(cloneableMap);  // 编译不通过 

与单一调度示例类似,这里重要的是参数的声明类型,而不是实际类型。另外,如果有多个方法对于给定的参数同样有效,Java会抛出一个编译错误,并强制你指定应该调用哪个方法。

多重继承-接口

  1. interface Father { 
  2.   default void print() { log.info("I am Father!"); } 
  3.   
  4. interface Mother { 
  5.   default void print() { log.info("I am Mother!"); } 
  6.   
  7. class Child implements Father, Mother {} 

下面打印的是什么?

  1. new Child().print(); 

与前面的示例类似,这个示例也编译不通过。具体地说,Child 的类定义本身将无法编译,因为在 Father 和 Mother 中存在冲突的缺省方法。你需要修改 Child 类指定 Child: : print 的行为。

多重继承-类和接口

  1. class ParentClass { 
  2.   void print() { log.info("I am a class!"); } 
  3.   
  4. interface ParentInterface { 
  5.   default void print() { log.info("I am an interface!"); } 
  6.   
  7. class Child extends ParentClass implements ParentInterface {} 

打印什么?

  1. new Child().print(); 

答案:

  1. new Child().print();  // 打印: "I am a class!" 

如果类和接口之间存在继承冲突,那么类方法优先。

传递性重写

  1. class Parent { 
  2.   void print() { foo(); } 
  3.   void foo() { log.info("I am Parent!"); } 
  4.   
  5. class Child extends Parent { 
  6.   void foo() { log.info("I am Child!"); } 

打印什么?

  1. new Child().print(); 

答案:

  1. new Child().print();  // 打印: "I am Child!" 

重写方法甚至对传递调用也会生效,阅读 Parent 类的人可能认为 Parent: : print 总是会调用 Parent: : foo。但是如果该方法被重写,那么 Parent: : print 将调用重写后的 foo ()版本。

私有重写

  1. class Parent { 
  2.   void print() { foo(); } 
  3.   private void foo() { log.info("I am Parent!"); } 
  4.   
  5. class Child extends Parent { 
  6.   void foo() { log.info("I am Child!"); } 

打印什么?

  1. new Child().print(); 

答案:

  1. new Child().print();  // 打印: "I am Parent!" 

除了一点不同之外,这个与前一个例子完全相同。现在将 Parent.foo()声明为 private。因此,当 Parent.print()调用 foo()时,不管子类中是否存在 foo()的其他实现,也不管调用 print()的实例的实际类型如何。

静态重写

  1. class Parent { 
  2.   static void print() { log.info("I am Parent!"); } 
  3.   
  4. class Child extends Parent { 
  5.   static void print() { log.info("I am Child!"); } 

打印什么?

  1. Child child = new Child(); 
  2. Parent parent = child
  3.   
  4. parent.print(); 
  5. child.print(); 

答案:

  1. parent.print(); // 打印: "I am Parent!" 
  2. child.print();  // 打印: "I am Child!" 

Java 不允许重写静态方法。如果在父类和子类中定义了相同的静态方法,那么实例的实际类型根本不重要。只有声明的类型用于确定调用两个方法中的哪一个。

这是使用@override注解标记所有重写方法的另一个原因。在上面的例子中,在向 Child: : print 添加注解时,你会得到一个编译错误,告诉你由于方法是静态的,因此无法重写该方法。

静态链接

  1. class Parent { 
  2.   void print() { staticMethod(); instanceMethod(); } 
  3.   static void staticMethod() { log.info("Parent::staticMethod"); } 
  4.   void instanceMethod() { log.info("Parent::instanceMethod"); } 
  5.   
  6. class Child extends Parent { 
  7.   static void staticMethod() { log.info("Child::staticMethod"); } 
  8.   void instanceMethod() { log.info("Child::instanceMethod"); } 

打印什么?

  1. Child child = new Child(); 
  2. child.print(); 

答案:

  1. Parent::staticMethod 
  2. Child::instanceMethod 

这是我们之前讨论过的一些不同概念的组合。例如,即使调用方位于父方法中,重写也会生效。但是,对于静态方法,即使变量的声明类型是 Child,也要调用 Parent: : staticMethod,因为有中间 print ()方法。

总结

如果说有什么值得注意的地方,那就是继承非常非常棘手,而且很容易出错。

 

责任编辑:赵宁宁 来源: 科技缪缪
相关推荐

2021-06-10 18:59:41

Java编程语言

2017-09-06 15:40:36

大数据动向

2023-09-22 22:49:15

C++重载重写

2022-01-11 06:53:23

面试重写重载

2009-08-25 17:15:50

C#隐藏C#重写C#重载

2020-06-17 12:22:44

C覆盖重载

2023-05-29 08:32:40

JAVA重写重载

2017-10-31 10:12:12

无人驾驶安全性乘客信任

2017-09-28 09:46:56

5G通信互联

2019-07-24 10:11:51

jdkjreJava

2016-11-02 16:13:19

代码开发技能

2023-12-05 15:24:46

2016-03-28 09:39:54

2009-08-28 15:28:22

C# overridenew隐藏

2020-09-14 15:57:53

Vue和React

2021-03-31 10:54:04

Windows 10Windows微软

2023-11-27 00:48:46

displayvisibility

2024-01-01 08:25:53

ViewSurface框架

2021-12-31 18:24:45

ThreadLocal数据库对象

2015-08-17 13:07:48

数据
点赞
收藏

51CTO技术栈公众号