看下面的代码先不要运行而尝试给出输出:
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的优化结果。
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的优化结果。
上面的总结下来就是如下
- 如果父类有静态成员赋值或者静态初始化块,执行静态成员赋值和静态初始化块
- 如果类有静态成员赋值或者静态初始化块,执行静态成员赋值和静态初始化块
- 将类的成员赋予初值(原始类型的成员的值为规定值,例如int型为0,float型为0.0f,boolean型为false;对象类型的初始值为null)
- 如果构造方法中存在this()调用(可以是其它带参数的this()调用)则执行之,执行完毕后进入第7步继续执行,如果没有this调用则进行下一步。(这个有可能存在递归调用其它的构造方法)
- 执行显式的super()调用(可以是其它带参数的super()调用)或者隐式的super()调用(缺省构造方法),此步骤又进入一个父类的构造过程并一直上推至Object对象的构造。
- 执行类申明中的成员赋值和初始化块。
- 执行构造方法中的其它语句。