让你怀疑人生的重载和重写的区别
内容90%翻译修改自https://software.rajivprab.com/2019/08/14/nuances-of-overloading-and-overriding-in-java/
如果你认为你对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::staticMethod
Child::instanceMethod
这是我们之前讨论过的一些不同概念的组合。例如,即使调用方位于父方法中,重写也会生效。但是,对于静态方法,即使变量的声明类型是 Child,也要调用 Parent: : staticMethod,因为有中间 print ()方法。
总结
如果说有什么值得注意的地方,那就是继承非常非常棘手,而且很容易出错。
我的朋友群建立了,现在人数已经100多人了,如果你还没有加入的就快点加我好友备注”进群“吧。
往期推荐
点击二维码识别关注
点在看,让更多看见。
本文分享自微信公众号 - 科技缪缪(kejimiumiu)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
MySQL大表优化方案
背景 阿里云RDS FOR MySQL(MySQL5.7版本)数据库业务表每月新增数据量超过千万,随着数据量持续增加,我们业务出现大表慢查询,在业务高峰期主业务表的慢查询需要几十秒严重影响业务 方案概述 一、数据库设计及索引优化 MySQL数据库本身高度灵活,造成性能不足,严重依赖开发人员的表设计能力以及索引优化能力,在这里给几点优化建议 时间类型转化为时间戳格式,用int类型储存,建索引增加查询效率 建议字段定义not null,null值很难查询优化且占用额外的索引空间 使用TINYINT类型代替枚举ENUM 存储精确浮点数必须使用DECIMAL替代FLOAT和DOUBLE 字段长度严重根据业务需求来,不要设置过大 尽量不要使用TEXT类型,如必须使用建议将不常用的大字段拆分到其它表 MySQL对索引字段长度是有限制的, innodb引擎的每个索引列长度默认限制为767字节(bytes),所有组成索引列的长度和不能大于3072字节(mysql8.0单索引可以创建1024字符) 大表有DDL需求时请联系DBA 最左索引匹配规则 顾名思义就是最左优先,在创建组合索引时,要根据业务需求,...
- 下一篇
openEuler Summit 2020 来了!
openEuler Summit 是由 openEuler 社区举办的开发者交流会,首届线下 openEuler Summit 将于 12 月在北京举行。 openEuler Summit 广泛邀请操作系统生态的开发者、用户、社区贡献者、软件爱好者共同解读 openEuler 的最新版,探讨未来的技术路线,让技术、生态、商业在这里产生奇妙的化学反应。 开源是一种态度、分享是一种精神。Call for Speaker、Call for Sponsor、Call for SIG、Call for Demo 现已全面开放报名。 我们诚挚的邀请您报名演讲、演示 Demo、发表案例、参与社区建设。 期待您的到来。 openEuler —— 最具活力的开源社区 本文分享自微信公众号 - openEuler(openEulercommunity)。如有侵权,请联系 support@oschina.cn 删除。本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- CentOS关闭SELinux安全模块
- CentOS7,CentOS8安装Elasticsearch6.8.6
- Windows10,CentOS7,CentOS8安装Nodejs环境
- CentOS7安装Docker,走上虚拟化容器引擎之路
- Linux系统CentOS6、CentOS7手动修改IP地址
- CentOS6,CentOS7官方镜像安装Oracle11G
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题