`
hesihua
  • 浏览: 228811 次
  • 性别: Icon_minigender_2
  • 来自: 武汉
社区版块
存档分类
最新评论

Java类中构造方法的执行顺序和变量初始化

    博客分类:
  • java
 
阅读更多
看下面的代码先不要运行而尝试给出输出:
class A {
        public A() {
                init();
        }
        public void init() {
        }
}
public class B extends A {
        int i;
        int s = 0;
        public void init() {
                i = 100;
                s = 100;
        }
        public void println() {
                System.out.println(i);
                System.out.println(s);
        }
        public static void main(String[] arg) {
                new B().println();
        }
}
它的输出是什么呢?为什么不输出 100 100,而输出 100 0呢?
可以用下面的代码来尝试解释:
class A {
        public A() {
                System.out.println("enter A()");
                init();
                System.out.println("exit A()");
        }
        public void init() {
                System.out.println("enter A.init");
                System.out.println("exit A.init");
        }
}
public class B extends A {
        public B() {
                System.out.println("enter B()");
                System.out.println("exit B()");
        }
        int i;
        int s = inits();
        public static int inits() {
                System.out.println("enter B.inits");
                System.out.println("exit B.inits");
                return 0;
        }
        public void init() {
                System.out.println("enter B.init");
                i = 100;
                s = 100;
                System.out.println("exit B.init");
        }
        public void println() {
                System.out.println("enter B.println");
                System.out.println(i);
                System.out.println(s);
                System.out.println("exit B.println");
        }
        public static void main(String[] arg) {
                new B().println();
        }
}

上面的代码输出如下:
enter A()
enter B.init
exit B.init
exit A()
enter B.inits
exit B.inits
enter B()
exit B()
enter B.println
100
0
exit B.println

由此可以看出大致执行顺序如下:
main的new B()
->class B的public B()的第一行(首先调用基类构造函数,隐含的super()调用),第二行还没执行又
->class A的public A()第一行,第二行init()去调用class B的init()而不是class A的init()所以
  这里i=100,s=100(运行时多态性),public A()完了之后
->public B()的第一行,下面先执行实例变量的初始化。(此处在下面继续讨论)
  下来是s=inits()结果s=0,i没变还是100,最后才执行public B()的两条输出,到这里new B()才算完,
  下面就是B的println()。

关于i和s在类初始化方面的赋值方面的问题,请继续看下面的例子:

class Base {
    Base() {
        System.out.println("Base() before print()");
        print();
        System.out.println("Base() after print()");
    }
    public void print() {
        System.out.println("Base.print()");
    }
}
class Derived extends Base {
    int value = 100;
    Derived() {
        System.out.println("Derived() With " + value);
    }
    public void print() {
        System.out.println("Derived.print() with " + value);
    }
}
public class Main {
    public static void main(String[] args) {
        new Derived();
    }
}
如果变量有定义初始化值,如value=100,则先赋初始值,然后运行构造函数,那么在这个程
序的任何位置value都应该是100,但事实却非如此,输出结果如下:
Base() before print()
Derived.print() with 0   <---------这里是0而不是100
Base() after print()
Derived() With 100

会不会比较容易让人迷惑?
总结一下吧,顺序当然是很容易就推出,没什么好讨论的。
实际上例子只是说明,
int i; != int i = 0;
一般的初学者都会认为两者是相同的。
但是实际上不但是在顺序上不一样,而且javac对两者的编译是完全不一样。
前者只是申明一个变量,在初始化对象变量(这里指int i = 0;)的时候并不会编译成初始化指令。
而这些初始化对象变量的指令,会在本类构造函数里面的第一条指令(注意不是构造函数之前)
之前执行,而在此之前可能已经执行了父类的构造函数。
所以我们不难推出最开始那个例子的结果为什么一个是100,一个是0。

还有要注意的是构造函数实际上并没有分配空间(尽管我们通常都会认为)。
对于一般的对象生成(用new关键字,其他情况要另外分析)。
javac会把它编译成new #number 这个指令,#number指向的是类在常数池的索引。
这个new指令就是分配对象空间,并根据类里面所声明的变量进行空间分配,
并把他们赋值成初始化的值(就是大家都知道的,int(0),objct(null))。

举个简单的例子。对于一般的语句:比如说new A();
实际上执行顺序如下:
        new #A的索引
//然后是下面大括号的指令,它们都是A的构造函数(这里的构造函数并不等同于我们代码
                                里面的public A() {.. },实际上是大于,然后
                                根据里面的代码生成A的构造函数字节代码段。)
        {
         执行父类构造函数字节代码段
         本类对象变量的初始化指令(比如int i = 10;这些指令是在编译时确定的)
         然后下面的指令就是public A() {...}里面代码的指令
              {
                ...
                ...
              }
        }

实际上,假如你只是在类申明了int i;而在以后的代码都不引用它的话,
javac是不会把它编译到class里面的。这也许是javac的优化结果。
上文摘抄自: http://www.cnitblog.com/flydream/archive/2006/11/11/19074.aspx
上面的总结下来就是如下
  1. 如果父类有静态成员赋值或者静态初始化块,执行静态成员赋值和静态初始化块
  2. 如果类有静态成员赋值或者静态初始化块,执行静态成员赋值和静态初始化块
  3. 将类的成员赋予初值(原始类型的成员的值为规定值,例如int型为0,float型为0.0f,boolean型为false;对象类型的初始值为null)
  4. 如果构造方法中存在this()调用(可以是其它带参数的this()调用)则执行之,执行完毕后进入第7步继续执行,如果没有this调用则进行下一步。(这个有可能存在递归调用其它的构造方法)
  5. 执行显式的super()调用(可以是其它带参数的super()调用)或者隐式的super()调用(缺省构造方法),此步骤又进入一个父类的构造过程并一直上推至Object对象的构造。
  6. 执行类申明中的成员赋值和初始化块。
  7. 执行构造方法中的其它语句。
分享到:
评论

相关推荐

    java 静态非静态 字段方法 子类父类构造_初始化顺序!

    java 静态_非静态 字段_方法_代码块 子类父类构造_初始化顺序! 三个class 让你清清楚楚 第一个class java代码如下: package initialOrder; class Parent { // 静态变量 public static String p_StaticField...

    java面试题-类的初始化顺序.doc

    大家在去参加面试的时候,经常会遇到这样的考题:给你两个类的代码,它们之间是继承的关系,每个类里只有构造器方法和一些变量,构造器里可能还有一段代码对变量值进行了某种运算,另外还有一些将变量值输出到控制台...

    Java类初始化顺序

    所有代码块是从上往下顺序执行的,所以代码块里面使用到的变量如果在块下面初始化会有问题 执行构造方法中内容。 所以看见的空构造方法,只能说第三部没有需要执行的内容。 下面举例子 public class TestClass { ...

    JAVA基础知识精华总结 收藏

    未创建此类对象)的静态对象时,所有的静态变量也要按它们在类中的顺序初始化。 2、 继承时,对象的初始化过程 (1) 主类的超类由高到低按顺序初始化静态成员,无论静态成员是否为private。 (2) 主类静态成员的...

    Java开发者文档,分享日常学习的一些小知识点 .rar

     未创建此类对象)的静态对象时,所有的静态变量也要按它们在类中的顺序初始化。  2、继承时,对象的初始化过程  (1) 主类的超类由高到低按顺序初始化静态成员,无论静态成员是否为private。  (2) 主类静态...

    Java 基础核心总结 +经典算法大全.rar

    类的初始化 成员初始化 构造器初始化初始化顺序 数组初始化 对象的销毁 对象作用域 this 和 super 访问控制权限继承 多态组合代理 向上转型static final 接口和抽象类接口 抽象类异常 认 识 Exception 什么是 ...

    JAVA面试题解惑系列

    我们大家都知道,对于静态变量、静态初始化块、变量、初始化块、构造器,它们的初始化顺序依次是(静态变量、静态初始化块)&gt;(变量、初始化块)&gt;构造器。我们也可以通过下面的测试代码来验证这一点:

    Java 语言基础 —— 非常符合中国人习惯的Java基础教程手册

    生成对象的最后一步是执行构造方法,进行初始化。由于对构造方法可以进行重写 ,所以通过给出不同个数或类型的参数会分别调用不同的构造方法。 例子:以类 Rectangle 为例,我们生成类 Rectangle 的对象: Rectangle p1...

    Java开发技术大全(500个源代码).

    errorInit.java 演示变量初始化错误的程序 integerExample.java 演示各种整型变量的使用 isPrime.java 判断素数 leapYearByIf.java 用if语句判断闰年 leapYearByLogical.java 用逻辑表达式判断闰年 lowToUpper...

    JAVA入门1.2.3:一个老鸟的JAVA学习心得 PART1(共3个)

    6.2.1 在类中给每个变量一个初始值 147 6.2.2 定义自己的引用 147 6.2.3 使用点操作符的技巧 148 6.2.4 类的数组 149 6.3 小结:Java其实是个类和对象的世界 152 6.4 习题 153 第7章 Java中的方法——给汽车...

    疯狂JAVA讲义

    5.3.2 成员变量的初始化和内存中的运行机制 128 5.3.3 局部变量的初始化和内存中的运行机制 130 5.3.4 变量的使用规则 130 5.4 隐藏和封装 132 5.4.1 理解封装 132 5.4.2 使用访问控制符 132 5.4.3 package和...

    Java入门1·2·3:一个老鸟的Java学习心得.PART3(共3个)

    6.2.1 在类中给每个变量一个初始值 147 6.2.2 定义自己的引用 147 6.2.3 使用点操作符的技巧 148 6.2.4 类的数组 149 6.3 小结:Java其实是个类和对象的世界 152 6.4 习题 153 第7章 Java中的方法——给汽车...

    java初学者必看

    3.5.2 变量赋值和初始化 3.5.3 常量 3.6 类型转化 3.6.1 数值类型之间的转换 3.6.2 强制类型转换 3.7 运算符 3.7.1 算术运算符 3.7.2 关系运算符 3.7.3 逻辑运算符 3.7.4 位运算符 3.7.5 自动递增和递减 ...

    java 面试题 总结

    派生类可以从它的基类那里继承方法和实例变量,并且类可以修改或增加新的方法使之更适合特殊的需要。 3.封装: 封装是把过程和数据包围起来,对数据的访问只能通过已定义的界面。面向对象计算始于这个基本概念,即...

    Java入门教程(微学苑)-part1

    3.18 Java类的基本运行顺序 53 3.19 Java包装类、拆箱和装箱详解 54 3.20 包装类的应用 54 3.20.1.1 1) 实现 int 和 Integer 的相互转换 54 3.20.1.2 2) 将字符串转换为整数 55 3.20.1.3 3) 将整数转换为字符串 55 ...

    关于JVM的总结

    初始化:在准备阶段已经赋过一个系统要求的初始值,而在初始化阶段则通过程序制定的主管计划去初始化变量和其他资源,从另一个角度理解就是 执行类构造器的()方法 .()方法是由编译器自动收集类中的所有变量的复制动作和...

    java面试800题

    "类的初始化过程 当创建一个对象时,对象的各个变量根据其类型被设置为相应的默认初始值,然后调用构造方法,而每次调用构造方法都是要执行三个阶段: 1.调用超类的构造方法; 2.由初始化语句对给变量进行初始化...

    JAVA基础课程讲义

    静态初始化块(经常用来初始化类,加载类信息时执行!) 67 package 68 JDK中的主要包 68 import 68 eclipse的使用 69 继承(extend, inheritance) 70 为什么需要继承?继承的作用? 70 继承介绍 70 如何实现继承? ...

Global site tag (gtag.js) - Google Analytics