构造器是OOP的重要组成部分,很多人认为它很容易。只不过是new了一个对象而已。而think in java的作者却告诉我们,其实这并不容易。先看下面这个例子。在你没看结果之前,你觉得你的答案是对的么。

AD:2013云计算架构师峰会课程资料下载

构造器是OOP的重要组成部分,很多人认为它很容易。只不过是new了一个对象而已。而think in java的作者却告诉我们,其实这并不容易。

先看下面这个例子。在你没看结果之前,你觉得你的答案是对的么。


  1. package com.tudou.t1;
  2. class Meal {
  3. Meal() {
  4. System.out.println("meal");
  5. }
  6. }
  7. class Bread {
  8. Bread() {
  9. System.out.println("Bread");
  10. }
  11. }
  12. class Cheese {
  13. Cheese() {
  14. System.out.println("Cheese");
  15. }
  16. }
  17. class Lettuce {
  18. Lettuce() {
  19. System.out.println("Lettuce");
  20. }
  21. }
  22. class Lunch extends Meal{
  23. Lunch() {
  24. System.out.println("Lunch");
  25. }
  26. }
  27. class PortableLunch extends Lunch{
  28. PortableLunch() {
  29. System.out.println("PortableLunch");
  30. }
  31. }
  32. public class Sandwich extends PortableLunch {
  33. private Bread b = new Bread();
  34. private Cheese c = new Cheese();
  35. private Lettuce l = new Lettuce();
  36. public Sandwich() {
  37. System.out.println("Sandwich");
  38. }
  39. public static void main(String[] args) {
  40. new Sandwich();
  41. }
  42. }

控制台的打印结果为:

meal
Lunch
PortableLunch
Bread
Cheese
Lettuce
Sandwich

复杂对象调用构造器的顺序应该遵循下面的原则:

1、调用基类[即父类]构造器。这个步骤会不断反复递归下去,首先是构造器这种层次结构的根,然后是下一层导出类[即子类],等等。直到最底层的导出类。[从最上层的meal一直递归到PortableLunch]

2、按声明顺序调用成员的初始化方法。[即上面的Bread,Cheese,Lettuce]

3、调用导出类构造器的主体[即Sandwich]

可见,调用类本身是最后完成初始化的,最先完成初始化的是最顶级的基类,所谓没有父亲,哪来的儿子。处于它们中间的是调用类本身拥有的子对象。因为你不可能在子对象初始化之前用本类调用它,所以它一定在本类调用之前,父类调用之后完成初始化的。

那么这个说法是不是一定成立呢。结果是否定的。你必须知道JVM的编绎原理才可能知道,它究竟是如何工作的。

我们来看下面这个例子,来解释为什么它不一定。因为在继承和重写的时候,这种情况变得有点诡异。

深入探究:


  1. package com.tudou.t1;
  2. public class ConstrcutorTest2 {
  3. public static void main(String[] args) {
  4. new RoundGlyph(5);
  5. }
  6. }
  7. class Glyph {
  8. void draw() {
  9. System.out.println("Glyph draw()");
  10. }
  11. Glyph() {
  12. System.out.println("Glyph before draw();");
  13. draw();
  14. System.out.println("Glyph after draw();");
  15. }
  16. }
  17. class RoundGlyph extends Glyph {
  18. private int radius = 1;
  19. RoundGlyph(int r) {
  20. radius = r;
  21. System.out.println("RoundGlyph(),radius:" + radius);
  22. }
  23. void draw() {
  24. System.out.println("RoundGlyph.draw(),radius:" + radius);//此处打印是0,而不是1
  25. }
  26. }

控制台打印结果:

Glyph before draw();
RoundGlyph.draw(),radius:0
Glyph after draw();
RoundGlyph(),radius:5

为什么RoundGlyph.draw(),radius:0这里会是0呢。

默认的1哪去了?值自己会变么。其实上面的讲述并不完整。,而这正是解决谜题的关键所在。初始化的实际过程之前,实际在还有一步。

0:在其他任何事物发生之前,将分配对象的存舍得空间初始化为二进制的零。

而它后面的初始化顺序就是上面的3步。

  1. 调用基类[即父类]构造器。这个步骤会不断反复递归下去,首先是构造器这种层次结构的根,然后是下一层导出类[即子类],等等。直到最底层的导出类。
  2. 按声明顺序调用成员的初始化方法。
  3. 调用导出类构造器的主体

也就是说,实际上有4步,知道这些你对对象初始化构造器才可能有个清楚的认识。

JAVA有更多的精髓等着人们去挖掘,而不仅仅是知道如何去使用它。

因为你不知道什么时候它会出现意想不到的后果,而这个错误,可能你根本就想不出来。

编写构造器时有一条准则:

用尽可能简单的方法使对象进入正常状态,如果可以的话,避免调用其它方法。

在构造器内唯一能够安全调用的那些方法是基类中的final或者private方法,这些方法不能被覆盖,因此也就不会出现令人惊讶的问题。

你可能无法总是遵循这条准则,但是应该朝着它努力。

学任何语言,请打好基础,它是你以后扩展的人生基石。

原文链接:http://blog.csdn.net/yaerfeng/article/details/7294882

Think in Java之构造器的真正调用顺序的更多相关文章

  1. Java类加载及实例化的调用顺序

    标题起得略拗口,大概意思就是说在一个Java类中,域和构造方法的调用顺序. 1. 没有继承的情况 单独一个类的场景下,初始化顺序为依次为 静态数据,继承的基类的构造函数,成员变量,被调用的构造函数. ...

  2. Java父类与子类方法调用顺序

    父类 FatherClass package 父类与子类方法调用顺序; /** * 父类 * @author shundong * */ public class FatherClass { priv ...

  3. java 类加载及实例化的调用顺序

    1.没有继承的情况 单独一个类的场景下,初始化顺序为依次为 静态变量和静态代码块(看两者的书写顺序),继承的基类的构造函数,成员变量,被调用的构造函数. 代码呈现: public class Test ...

  4. java实例变量及方法调用顺序

    public class Base { private String name="base"; public Base(){ sayHello(); } void sayHello ...

  5. Java构造器:级联调用,调用兄弟构造器

    级联调用: class Father{ Father(){ System.out.println("Father birth"); } public void announce() ...

  6. Java构造器的调用顺序

    <Java编程思想>中对构造器的调用顺序有如下描述: “构造器实际上是static方法,只不过该static声明是隐式的.” “基类的构造器总是在导出类的构造过程中被调用,而且按照继承层次 ...

  7. java中构造器的调用顺序

    在编程的过程中,我们经常会遇到多个类的继承问题,那么多个类的构造器是按照什么顺序调用的呢? 先看一段代码: public class Meal { public Meal() { System.out ...

  8. Java私有构造器

    Java私有构造器:使用private关键字声明的构造函数.由于类的构造函数时私有的,所以此类不能被实例化,同时也不能被继承.<Effective Java>第三条:用私有构造器或者枚举强 ...

  9. java 父类构造器

    当创建任何java对象时,程序总会首先调用系统的父类非静态初始化块(隐式执行)和父类构造器(从object开始(java程序中所有类的最终父类都是java.lang.Object类,使用语句super ...

  10. Java :构造器中的显式参数和this隐式参数

    1.构造器 写一个Java类,首先要先从构造器开始,构造器与类同名,在构造类的对象时会先从构造器开始. 构造器总是伴随着new操作符的执行而被调用. 构造器主要是用来初始化类的实例域. 构造器的特点: ...

随机推荐

  1. Java多线程并发编程中并发容器第二篇之List的并发类讲解

    Java多线程并发编程中并发容器第二篇之List的并发类讲解 概述 本文我们将详细讲解list对应的并发容器以及用代码来测试ArrayList.vector以及CopyOnWriteArrayList ...

  2. 即构音视频 Express Flutter SDK 全面支持空安全

    Flutter 是一套移动 UI 框架,可以快速在 iOS.Android 上构建高质量的原生用户界面.作为其官方语言 Dart 是类型安全的,当开发者获取变量的时候,编译器可以保证变量的类型,但类型 ...

  3. 音视频处理三剑客之 AEC:回声产生原因及回声消除原理

    在上一期课程<音视频开发者进阶 -- 音频要素>中,我们从声音三要素.音频模拟信号的数字化和音频数字信号特征等方面,重新认识了"声音"这个老朋友.今天,我们会进一步聊聊 ...

  4. 小tips:CSS3中的background-clip属性(背景的裁剪区域)

    CSS3中的background-clip属性,其主要是用来确定背景的裁剪区域,换句话说,就是如何控制元素背景显示区域. 语法如下: background-clip : border-box || p ...

  5. 免费在线OCR识别工具TextIn Tools,开启智能学习新时代

    传统的学习方式,笔记必须手写摘抄:带字照片只能插入文档:PDF转换要花钱买会员-- 而在线OCR识别工具tools.textin.com,既好用又免费,它不仅仅具有文字和表格识别工具,还包含PDF转文 ...

  6. Angular 18+ 高级教程 – Signals

    前言 首先,我必须先说明清楚.Signal 目前不是 Angular 的必备知识. 你的项目不使用 Signal 也不会少了条腿,断了胳膊. Angular 官方维护的 UI 组件库 Angular ...

  7. [TK] 颜色

    谴责这道题发明在线莫队的人,简直就是异端,还好我给在线莫队卡了,支持正义制裁 题意简述 给定序列,设 \(f(l,r,x)\) 表示 \(x\) 在 \([l,r]\) 内的出现次数,对给定 \(l, ...

  8. Nacos 开启鉴权设置

    Nacos 开启鉴权设置 # 开启鉴权 nacos.core.auth.enabled=true nacos.core.auth.system.type=nacos # 开启鉴权之后,你可以自定义用于 ...

  9. 数据库周刊59丨GaussDB(for openGauss)开放商用;人大金仓保障冬奥会演练顺利完成;MDL锁导致的MySQL问题分析;PG日志使用手册;达梦表空间查询;数据库笔试题面试题集……

    热门资讯 1.openGauss 上线华为云正式商用 - GaussDB(for openGauss) [摘要]近日,GaussDB(for openGauss)已于华为云官网全面开放商用.该产品是华 ...

  10. ADO.NET 连接数据库 【vs2022 + sqlServer】

    using System.Data; using System.Data.SqlClient; namespace Zhu.ADO.NET { internal class Program { pri ...