19

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

 3 years ago
source link: http://developer.51cto.com/art/202011/630453.htm
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

单一调度

class Parent { void print(String a) { log.info("Parent - String"); } void print(Object a) { log.info("Parent - Object"); }} class Child extends Parent { void print(String a) { log.info("Child - String"); } void print(Object a) { log.info("Child - Object"); }}

下面将会打印什么?

String string = "";Object stringObject = string; // 打印什么?Child child = new Child();child.print(string);child.print(stringObject); Parent parent = new Child();parent.print(string);parent.print(stringObject);

答案:

child.print(string); // 打印: "Child - String"child.print(stringObject); // 打印: "Child - Object" parent.print(string); // 打印: "Child - String"parent.print(stringObject); // 打印: "Child - Object"

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

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

隐式重写

class Parent { void print(Object a) { log.info("Parent - Object"); }} class Child extends Parent { void print(String a) { log.info("Child - String"); }}

打印什么?

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

答案:

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

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

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

显式重写

class Parent { void print(Object a) { log.info("Parent - Object!"); } void print(String a) { throw new RuntimeException(); }} class Child extends Parent { void print(String a) { log.info("Child - String!"); }}

打印什么?

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

答案:

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

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

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

模糊参数

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

下面打印的是什么?

HashMap cloneableMap = new HashMap();Cloneable cloneable = cloneableMap;Map map = cloneableMap; // What gets printed?Foo foo = new Foo();foo.print(map);foo.print(cloneable);foo.print(cloneableMap);

答案:

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

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

多重继承-接口

interface Father { default void print() { log.info("I am Father!"); }} interface Mother { default void print() { log.info("I am Mother!"); }} class Child implements Father, Mother {}

下面打印的是什么?

new Child().print();

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

多重继承-类和接口

class ParentClass { void print() { log.info("I am a class!"); }} interface ParentInterface { default void print() { log.info("I am an interface!"); }} class Child extends ParentClass implements ParentInterface {}

打印什么?

new Child().print();

答案:

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

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

传递性重写

class Parent { void print() { foo(); } void foo() { log.info("I am Parent!"); }} class Child extends Parent { void foo() { log.info("I am Child!"); }}

打印什么?

new Child().print();

答案:

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

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

私有重写

class Parent { void print() { foo(); } private void foo() { log.info("I am Parent!"); }} class Child extends Parent { void foo() { log.info("I am Child!"); }}

打印什么?

new Child().print();

答案:

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

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

静态重写

class Parent { static void print() { log.info("I am Parent!"); }} class Child extends Parent { static void print() { log.info("I am Child!"); }}

打印什么?

Child child = new Child();Parent parent = child; parent.print();child.print();

答案:

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

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

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

静态链接

class Parent { void print() { staticMethod(); instanceMethod(); } static void staticMethod() { log.info("Parent::staticMethod"); } void instanceMethod() { log.info("Parent::instanceMethod"); }} class Child extends Parent { static void staticMethod() { log.info("Child::staticMethod"); } void instanceMethod() { log.info("Child::instanceMethod"); }}

打印什么?

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

答案:

Parent::staticMethodChild::instanceMethod

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

总结

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

【责任编辑:赵宁宁 TEL:(010)68476606】


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK