构造器是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. uniapp中,getApp()返回的实例到底是什么?为什么getApp()返回的实例无法访问vuex的$store

    按uniapp官方手册中说,getApp()函数用于获取当前应用实例.当前应用,也就是说当前应用程序.因为getApp()返回的实例可以用于访问app.vue中的globaldata,因此这个当前应用 ...

  2. Python 潮流周刊#67:uv 的重磅更新(摘要)

    本周刊由 Python猫 出品,精心筛选国内外的 250+ 信息源,为你挑选最值得分享的文章.教程.开源项目.软件工具.播客和视频.热门话题等内容.愿景:帮助所有读者精进 Python 技术,并增长职 ...

  3. SQL Server – 基本操作 Table 和 Column

    前言 日常都是用 EF Core 来管理 Database, 偶尔也用 Management Studio, 就是很少手写 Command. 虽然网上一拉就有很多, 但是每次写单侧都要到处找还是挺烦的 ...

  4. ASP.NET Core – Razor Pages 冷知识

    Multiple Form Binding 问题 在一个 page 里面有 2 张 form, 那么就会有 2 个 model binding. 当任何一个 submit 的时候. 由于 2 个 mo ...

  5. OData – Routing

    前言 以前我都是把 ODataController 和普通 API Controller 分开做. (因为 OData 实在多 Bug, 好东西尽量不要掺和它) Read 的部分用 OData, CU ...

  6. cortex-m3 m4 异常机制

    文章写的很好,待整理 1.[STM32]HardFault问题详细分析及调试笔记 https://blog.csdn.net/m0_54916619/article/details/129979222 ...

  7. 系统编程-进程-先后fork或open一个文件的区别

    关联博文: 当文件操作遇上fork Linux内核的文件结构体 struct file { ......... struct path f_path; //文件的路径 #define f_dentry ...

  8. SuperMap iServer新增支持FlatGeobuf数据格式,查询渲染性能提升2-3倍

    导语 FlatGeobuf是一种地理数据存储格式,采用了二进制编码,相比其他文本或XML格式更高效,可以显著减小文件大小,这使得数据的传输和存储更加快速和高效. SuperMap iServer 11 ...

  9. template<> 模板特化

    template<> 是用于 模板特化(Template Specialization) 的一种语法. 模板特化允许你为某些特定的模板参数提供不同的实现.例如,template<&g ...

  10. 6. CSS有哪些方法可以提升层级

    1. 使用 z-index 2. 使用定位,脱离标准流