Think in Java之构造器的真正调用顺序
构造器是OOP的重要组成部分,很多人认为它很容易。只不过是new了一个对象而已。而think in java的作者却告诉我们,其实这并不容易。先看下面这个例子。在你没看结果之前,你觉得你的答案是对的么。
构造器是OOP的重要组成部分,很多人认为它很容易。只不过是new了一个对象而已。而think in java的作者却告诉我们,其实这并不容易。
先看下面这个例子。在你没看结果之前,你觉得你的答案是对的么。
- package com.tudou.t1;
- class Meal {
- Meal() {
- System.out.println("meal");
- }
- }
- class Bread {
- Bread() {
- System.out.println("Bread");
- }
- }
- class Cheese {
- Cheese() {
- System.out.println("Cheese");
- }
- }
- class Lettuce {
- Lettuce() {
- System.out.println("Lettuce");
- }
- }
- class Lunch extends Meal{
- Lunch() {
- System.out.println("Lunch");
- }
- }
- class PortableLunch extends Lunch{
- PortableLunch() {
- System.out.println("PortableLunch");
- }
- }
- public class Sandwich extends PortableLunch {
- private Bread b = new Bread();
- private Cheese c = new Cheese();
- private Lettuce l = new Lettuce();
- public Sandwich() {
- System.out.println("Sandwich");
- }
- public static void main(String[] args) {
- new Sandwich();
- }
- }
控制台的打印结果为:
meal
Lunch
PortableLunch
Bread
Cheese
Lettuce
Sandwich
复杂对象调用构造器的顺序应该遵循下面的原则:
1、调用基类[即父类]构造器。这个步骤会不断反复递归下去,首先是构造器这种层次结构的根,然后是下一层导出类[即子类],等等。直到最底层的导出类。[从最上层的meal一直递归到PortableLunch]
2、按声明顺序调用成员的初始化方法。[即上面的Bread,Cheese,Lettuce]
3、调用导出类构造器的主体[即Sandwich]
可见,调用类本身是最后完成初始化的,最先完成初始化的是最顶级的基类,所谓没有父亲,哪来的儿子。处于它们中间的是调用类本身拥有的子对象。因为你不可能在子对象初始化之前用本类调用它,所以它一定在本类调用之前,父类调用之后完成初始化的。
那么这个说法是不是一定成立呢。结果是否定的。你必须知道JVM的编绎原理才可能知道,它究竟是如何工作的。
我们来看下面这个例子,来解释为什么它不一定。因为在继承和重写的时候,这种情况变得有点诡异。
深入探究:
- package com.tudou.t1;
- public class ConstrcutorTest2 {
- public static void main(String[] args) {
- new RoundGlyph(5);
- }
- }
- class Glyph {
- void draw() {
- System.out.println("Glyph draw()");
- }
- Glyph() {
- System.out.println("Glyph before draw();");
- draw();
- System.out.println("Glyph after draw();");
- }
- }
- class RoundGlyph extends Glyph {
- private int radius = 1;
- RoundGlyph(int r) {
- radius = r;
- System.out.println("RoundGlyph(),radius:" + radius);
- }
- void draw() {
- System.out.println("RoundGlyph.draw(),radius:" + radius);//此处打印是0,而不是1
- }
- }
控制台打印结果:
Glyph before draw();
RoundGlyph.draw(),radius:0
Glyph after draw();
RoundGlyph(),radius:5
为什么RoundGlyph.draw(),radius:0这里会是0呢。
默认的1哪去了?值自己会变么。其实上面的讲述并不完整。,而这正是解决谜题的关键所在。初始化的实际过程之前,实际在还有一步。
0:在其他任何事物发生之前,将分配对象的存舍得空间初始化为二进制的零。
而它后面的初始化顺序就是上面的3步。
- 调用基类[即父类]构造器。这个步骤会不断反复递归下去,首先是构造器这种层次结构的根,然后是下一层导出类[即子类],等等。直到最底层的导出类。
- 按声明顺序调用成员的初始化方法。
- 调用导出类构造器的主体
也就是说,实际上有4步,知道这些你对对象初始化构造器才可能有个清楚的认识。
JAVA有更多的精髓等着人们去挖掘,而不仅仅是知道如何去使用它。
因为你不知道什么时候它会出现意想不到的后果,而这个错误,可能你根本就想不出来。
编写构造器时有一条准则:
用尽可能简单的方法使对象进入正常状态,如果可以的话,避免调用其它方法。
在构造器内唯一能够安全调用的那些方法是基类中的final或者private方法,这些方法不能被覆盖,因此也就不会出现令人惊讶的问题。
你可能无法总是遵循这条准则,但是应该朝着它努力。
学任何语言,请打好基础,它是你以后扩展的人生基石。
Think in Java之构造器的真正调用顺序的更多相关文章
- Java类加载及实例化的调用顺序
标题起得略拗口,大概意思就是说在一个Java类中,域和构造方法的调用顺序. 1. 没有继承的情况 单独一个类的场景下,初始化顺序为依次为 静态数据,继承的基类的构造函数,成员变量,被调用的构造函数. ...
- Java父类与子类方法调用顺序
父类 FatherClass package 父类与子类方法调用顺序; /** * 父类 * @author shundong * */ public class FatherClass { priv ...
- java 类加载及实例化的调用顺序
1.没有继承的情况 单独一个类的场景下,初始化顺序为依次为 静态变量和静态代码块(看两者的书写顺序),继承的基类的构造函数,成员变量,被调用的构造函数. 代码呈现: public class Test ...
- java实例变量及方法调用顺序
public class Base { private String name="base"; public Base(){ sayHello(); } void sayHello ...
- Java构造器:级联调用,调用兄弟构造器
级联调用: class Father{ Father(){ System.out.println("Father birth"); } public void announce() ...
- Java构造器的调用顺序
<Java编程思想>中对构造器的调用顺序有如下描述: “构造器实际上是static方法,只不过该static声明是隐式的.” “基类的构造器总是在导出类的构造过程中被调用,而且按照继承层次 ...
- java中构造器的调用顺序
在编程的过程中,我们经常会遇到多个类的继承问题,那么多个类的构造器是按照什么顺序调用的呢? 先看一段代码: public class Meal { public Meal() { System.out ...
- Java私有构造器
Java私有构造器:使用private关键字声明的构造函数.由于类的构造函数时私有的,所以此类不能被实例化,同时也不能被继承.<Effective Java>第三条:用私有构造器或者枚举强 ...
- java 父类构造器
当创建任何java对象时,程序总会首先调用系统的父类非静态初始化块(隐式执行)和父类构造器(从object开始(java程序中所有类的最终父类都是java.lang.Object类,使用语句super ...
- Java :构造器中的显式参数和this隐式参数
1.构造器 写一个Java类,首先要先从构造器开始,构造器与类同名,在构造类的对象时会先从构造器开始. 构造器总是伴随着new操作符的执行而被调用. 构造器主要是用来初始化类的实例域. 构造器的特点: ...
随机推荐
- ERR_INVALID_REDIRECT
原因为: response.sendRedirect(url) url地址写错了
- angular 知识
看完以下内容应该就可以上手Angular了 https://angular.io/start Udemy - Complete Angular 14 Course - Learn Frontend D ...
- EF Core – Custom Migrations (高级篇)
前言 会写这篇是因为最近开始大量使用 SQL Server Trigger 来维护冗余 (也不清楚这路对不对). EF Core migrations 没有支持 Trigger Github Issu ...
- C++ 数组元素操作
数组元素的移除 核心思路:创建一个新的内存空间存储移除后的数组,再将原数组delete释放,再将指针指向新数组. cout << "----------------------- ...
- c++面经系列0:开篇-c++岗位面试都会问些什么?
本文是C++岗位面试经验分享系列的开篇,敬请持续关注. 在C++岗位面试中,通常首先进行技术面试,若通过则会进行HR面试.HR面试的内容先暂且略过,未来我们会有机会深入探讨,今天我们主要聚焦于技术面试 ...
- NIO实现聊天室之:一切都要从网络编程的基础开始聊起!
一.写在开头 大家好,Build哥回来啦!停更了大概2个月之久,之前有段时间去写小说去了,后来又因为公司活太多,牛马干的太投入,就拉下了博客的更新,国庆节期间,难得的闲下来,准备回归老本行啦. 大致的 ...
- Oracle数据库安装配置详细教程汇总(含11g、12c、18c、19c、21c)
不论你是数据库小白,还是久经沙场的技术专家,你接触和运维Oracle数据库的第一步可能都是安装配置.并且随着软硬件的升级.替换以及业务场景的变化,数据库安装也将是你常常会进行的操作之一. 这里先为大家 ...
- px 、em、rem 的选取依据
1. px 像素(Pixel).绝对单位.像素px是相对于显示器屏幕分辨率而言的,是一个虚拟长度单位,是计算机系统的数字化图像长度单位,如果 px要换算成物理长度,需要 指定精度 DPI. 2. em ...
- 使用datagrip时,报错 connection refused
因为数据库没有开启 : 打开终端 输入(管理员身份): net start mysql80 (这是你的数据库的名字)
- Redis实现幂等、防抖、限流等功能
本文章主要讲述如何使用Redis实现幂等.防抖.限流等功能. 幂等组件 import lombok.RequiredArgsConstructor; import org.springframewor ...