zoukankan      html  css  js  c++  java
  • Think in Java之构造器的真正调用顺序

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

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

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

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

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

    控制台的打印结果为:

    meal 
    Lunch 
    PortableLunch 
    Bread 
    Cheese 
    Lettuce 
    Sandwich

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

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

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

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

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

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

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

    深入探究:

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

    控制台打印结果:

    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

  • 相关阅读:
    linux 安装软件三种方法
    megalo -- 网易考拉小程序解决方案
    层叠上下文 Stacking Context
    关于document.write
    学习块格式化上下文(BlockFormattingContext)
    jQuery 源码分析 8: 回头看jQuery的构造器(jQuery.fn,jQury.prototype,jQuery.fn.init.prototype的分析)
    简化版的Flappy Bird开发过程(不使用第三方框架)
    jQuery 源码分析 7: sizzle
    jQuery 源码分析6: jQuery 基本静态方法(二)
    jQuery 源码分析5: jQuery 基本静态方法(一)
  • 原文地址:https://www.cnblogs.com/daichangya/p/12958607.html
Copyright © 2011-2022 走看看