您的位置:首页 > 编程语言 > Java开发

Java父类与子类中静态代码块 实例代码块 静态变量 实例变量 构造函数执行顺序

2016-11-03 20:16 1006 查看
实例化子类时,父类与子类中的静态代码块、实例代码块、静态变量、实例变量、构造函数的执行顺序是怎样的?

代码执行的优先级为:

firest:静态部分

second:实例化过程

详细顺序为:

1.父类静态代码块与父类静态变量赋值(取决于代码书写顺序)

2.子类静态代码块与子类静态变量赋值(取决于代码书写顺序)

3.父类实例变量赋值与父类代码块(取决于代码书写顺序)

4.父类构造函数

5.子类实例变量赋值与子类代码块(取决于代码书写顺序)

6.子类构造函数

在JVM加载完类以后,类在被使用的时候初始化,静态部分只在类初始化的时候执行一次。

测试代码:

class Father {

Father() {
LogUtil.log(System.currentTimeMillis() + " ------ 父类构造函数");
}

static {
LogUtil.log(System.currentTimeMillis() + " ------ 父类静态代码块");
}

long x = getTime(" ------ 父类实例变量赋值");

{
long time = System.currentTimeMillis();
LogUtil.log(time + " ------ 父类代码块");
}

static long y = getTime(" ------ 父类静态变量赋值");

static long getTime(String who) {
long time = System.currentTimeMillis();
LogUtil.log(time + who);
return time;
}
}

class Child extends Father {

Child() {
LogUtil.log(System.currentTimeMillis() + " ------ 子类构造函数");
}

static long y = getTime(" ------ 子类静态变量赋值");

static {
LogUtil.log(System.currentTimeMillis() + " ------ 子类静态代码块");
}

{
long time = System.currentTimeMillis();
LogUtil.log(time + " ------ 子类代码块");
}

long x = getTime(" ------ 子类实例变量赋值");

static long getTime(String who) {
long time = System.currentTimeMillis();
LogUtil.log(time + who);
return time;
}
}


调用代码:

new Thread(new Runnable() {
@Override
public void run() {
new Child();
LogUtil.log("分隔符 ------ 分隔符");
new Child();
}
}).start();


执行结果:

11-03 20:02:03.350 7533-7881/? E/AKADDEMO: 1478174523353 ------ 父类静态代码块
11-03 20:02:03.350 7533-7881/? E/AKADDEMO: 1478174523353 ------ 父类静态变量赋值
11-03 20:02:03.350 7533-7881/? E/AKADDEMO: 1478174523353 ------ 子类静态变量赋值
11-03 20:02:03.350 7533-7881/? E/AKADDEMO: 1478174523353 ------ 子类静态代码块
11-03 20:02:03.350 7533-7881/? E/AKADDEMO: 1478174523353 ------ 父类实例变量赋值
11-03 20:02:03.350 7533-7881/? E/AKADDEMO: 1478174523353 ------ 父类代码块
11-03 20:02:03.350 7533-7881/? E/AKADDEMO: 1478174523353 ------ 父类构造函数
11-03 20:02:03.350 7533-7881/? E/AKADDEMO: 1478174523353 ------ 子类代码块
11-03 20:02:03.350 7533-7881/? E/AKADDEMO: 1478174523353 ------ 子类实例变量赋值
11-03 20:02:03.350 7533-7881/? E/AKADDEMO: 1478174523353 ------ 子类构造函数
11-03 20:02:03.350 7533-7881/? E/AKADDEMO: 分隔符 ------ 分隔符
11-03 20:02:03.350 7533-7881/? E/AKADDEMO: 1478174523353 ------ 父类实例变量赋值
11-03 20:02:03.350 7533-7881/? E/AKADDEMO: 1478174523353 ------ 父类代码块
11-03 20:02:03.350 7533-7881/? E/AKADDEMO: 1478174523353 ------ 父类构造函数
11-03 20:02:03.350 7533-7881/? E/AKADDEMO: 1478174523354 ------ 子类代码块
11-03 20:02:03.350 7533-7881/? E/AKADDEMO: 1478174523354 ------ 子类实例变量赋值
11-03 20:02:03.350 7533-7881/? E/AKADDEMO: 1478174523354 ------ 子类构造函数

-------------2018.1.5---------------

我们通过反编译工具(IntelliJ IDEA或JD-JUI)反编译一下class文件看能发现什么有意思的东西

测试代码:

class Father {

Father() {
System.out.print(System.currentTimeMillis() + " ------ 父类构造函数" + '\n');
}

private static long a = getTime(" -----a 父类静态变量赋值" + '\n');
private final static long b = getTime(" -----b 父类静态变量赋值" + '\n');

static {
System.out.print(System.currentTimeMillis() + " ------ 父类静态代码块" + '\n');
}

long aa = getTime(" ----aa 父类实例变量赋值" + '\n');

{
System.out.print(System.currentTimeMillis() + " ------ 父类代码块" + '\n');
}

long bb = getTime(" ----bb 父类实例变量赋值" + '\n');

static long z = getTime(" -----z 父类静态变量赋值" + '\n');

static long getTime(String who) {
long time = System.currentTimeMillis();
System.out.print(time + who);
return time;
}

private static long c = getTime(" -----c 父类静态变量赋值" + '\n');
private final static long d = getTime(" -----d 父类静态变量赋值" + '\n');
}
调用代码:

public static void main(String[] args) {
new Father();
}
执行结果:

1515134833612 -----a 父类静态变量赋值
1515134833614 -----b 父类静态变量赋值
1515134833614 ------ 父类静态代码块
1515134833614 -----z 父类静态变量赋值
1515134833614 -----c 父类静态变量赋值
1515134833614 -----d 父类静态变量赋值
1515134833614 ----aa 父类实例变量赋值
1515134833614 ------ 父类代码块
1515134833614 ----bb 父类实例变量赋值
1515134833614 ------ 父类构造函数
用 IntelliJ IDEA 打开上述代码编译之后的 Father.class :

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.ak.torch.videoplayer.disassamble;

public class Father {
private static long a = getTime(" -----a 父类静态变量赋值\n");
private static final long b = getTime(" -----b 父类静态变量赋值\n");
long aa = getTime(" ----aa 父类实例变量赋值\n");
long bb;
static long z;
private static long c;
private static final long d;

Father() {
System.out.print(System.currentTimeMillis() + " ------ 父类代码块" + '\n');
this.bb = getTime(" ----bb 父类实例变量赋值\n");
System.out.print(System.currentTimeMillis() + " ------ 父类构造函数" + '\n');
}

static long getTime(String var0) {
long var1 = System.currentTimeMillis();
System.out.print(var1 + var0);
return var1;
}

static {
System.out.print(System.currentTimeMillis() + " ------ 父类静态代码块" + '\n');
z = getTime(" -----z 父类静态变量赋值\n");
c = getTime(" -----c 父类静态变量赋值\n");
d = getTime(" -----d 父类静态变量赋值\n");
}
}
可以发现:

1、在 Father.java 中静态变量a、b的赋值操作写在了静态代码块的前面,静态变量z、c、d的赋值操作写在了静态代码块的后面,查看反编译之后的代码,可见静态变量z、c、d的赋值被写在了静态代码块中。

2、在 Father.java 中实例变量aa的赋值操作写在了实例代码块的前面,实例变量bb的赋值操作写在了实例代码块的后面,查看反编译之后的代码,可见代码块中的操作和实例变量b的赋值都被写在了构造函数中。

下面分情况删除一些代码,我们看一下反编译之后的不同

情况一:删掉静态代码块,此时的 Father.java 代码:

class Father {

Father() {
System.out.print(System.currentTimeMillis() + " ------ 父类构造函数" + '\n');
}

private static long a = getTime(" -----a 父类静态变量赋值" + '\n');
private final static long b = getTime(" -----b 父类静态变量赋值" + '\n');

long aa = getTime(" ----aa 父类实例变量赋值" + '\n');

{
System.out.print(System.currentTimeMillis() + " ------ 父类代码块" + '\n');
}

long bb = getTime(" ----bb 父类实例变量赋值" + '\n');

static long z = getTime(" -----z 父类静态变量赋值" + '\n');

static long getTime(String who) {
long time = System.currentTimeMillis();
System.out.print(time + who);
return time;
}

private static long c = getTime(" -----c 父类静态变量赋值" + '\n');
private final static long d = getTime(" -----d 父类静态变量赋值" + '\n');
}
用 IntelliJ IDEA 打开上述代码编译之后的 Father.class :

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.ak.torch.videoplayer.disassamble;

public class Father {
private static long a = getTime(" -----a 父类静态变量赋值\n");
private static final long b = getTime(" -----b 父类静态变量赋值\n");
long aa = getTime(" ----aa 父类实例变量赋值\n");
long bb;
static long z = getTime(" -----z 父类静态变量赋值\n");
private static long c = getTime(" -----c 父类静态变量赋值\n");
private static final long d = getTime(" -----d 父类静态变量赋值\n");

Father() {
System.out.print(System.currentTimeMillis() + " ------ 父类代码块" + '\n');
this.bb = getTime(" ----bb 父类实例变量赋值\n");
System.out.print(System.currentTimeMillis() + " ------ 父类构造函数" + '\n');
}

static long getTime(String var0) {
long var1 = System.currentTimeMillis();
System.out.print(var1 + var0);
return var1;
}
}
可以发现:

任何一个静态变量的赋值都没有被写在静态代码块中

情况二:删掉实例代码块,此时的 Father.java 代码:

class Father {

Father() {
System.out.print(System.currentTimeMillis() + " ------ 父类构造函数" + '\n');
}

private static long a = getTime(" -----a 父类静态变量赋值" + '\n');
private final static long b = getTime(" -----b 父类静态变量赋值" + '\n');

static {
System.out.print(System.currentTimeMillis() + " ------ 父类静态代码块" + '\n');
}

long aa = getTime(" ----aa 父类实例变量赋值" + '\n');

long bb = getTime(" ----bb 父类实例变量赋值" + '\n');

static long z = getTime(" -----z 父类静态变量赋值" + '\n');

static long getTime(String who) {
long time = System.currentTimeMillis();
System.out.print(time + who);
return time;
}

private static long c = getTime(" -----c 父类静态变量赋值" + '\n');
private final static long d = getTime(" -----d 父类静态变量赋值" + '\n');
}
用 IntelliJ IDEA 打开上述代码编译之后的 Father.class :

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.ak.torch.videoplayer.disassamble;

public class Father {
private static long a = getTime(" -----a 父类静态变量赋值\n");
private static final long b = getTime(" -----b 父类静态变量赋值\n");
long aa = getTime(" ----aa 父类实例变量赋值\n");
long bb = getTime(" ----bb 父类实例变量赋值\n");
static long z;
private static long c;
private static final long d;

Father() {
System.out.print(System.currentTimeMillis() + " ------ 父类构造函数" + '\n');
}

static long getTime(String var0) {
long var1 = System.currentTimeMillis();
System.out.print(var1 + var0);
return var1;
}

static {
System.out.print(System.currentTimeMillis() + " ------ 父类静态代码块" + '\n');
z = getTime(" -----z 父类静态变量赋值\n");
c = getTime(" -----c 父类静态变量赋值\n");
d = getTime(" -----d 父类静态变量赋值\n");
}
}


可以发现:

任何一个实例变量的赋值都没有被写在构造函数中

情况三:删掉静态变量,此时的 Father.java 代码:

class Father {

Father() {
System.out.print(System.currentTimeMillis() + " ------ 父类构造函数" + '\n');
}

static {
System.out.print(System.currentTimeMillis() + " ------ 父类静态代码块" + '\n');
}

long aa = getTime(" ----aa 父类实例变量赋值" + '\n');

{
System.out.print(System.currentTimeMillis() + " ------ 父类代码块" + '\n');
}

long bb = getTime(" ----bb 父类实例变量赋值" + '\n');

static long getTime(String who) {
long time = System.currentTimeMillis();
System.out.print(time + who);
return time;
}
}
用 IntelliJ IDEA 打开上述代码编译之后的 Father.class :

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.ak.torch.videoplayer.disassamble;

public class Father {
long aa = getTime(" ----aa 父类实例变量赋值\n");
long bb;

Father() {
System.out.print(System.currentTimeMillis() + " ------ 父类代码块" + '\n');
this.bb = getTime(" ----bb 父类实例变量赋值\n");
System.out.print(System.currentTimeMillis() + " ------ 父类构造函数" + '\n');
}

static long getTime(String var0) {
long var1 = System.currentTimeMillis();
System.out.print(var1 + var0);
return var1;
}

static {
System.out.print(System.currentTimeMillis() + " ------ 父类静态代码块" + '\n');
}
}


情况四:删掉实例变量,此时的 Father.java 代码:

class Father {

Father() {
System.out.print(System.currentTimeMillis() + " ------ 父类构造函数" + '\n');
}

private static long a = getTime(" -----a 父类静态变量赋值" + '\n');
private final static long b = getTime(" -----b 父类静态变量赋值" + '\n');

static {
System.out.print(System.currentTimeMillis() + " ------ 父类静态代码块" + '\n');
}

{
System.out.print(System.currentTimeMillis() + " ------ 父类代码块" + '\n');
}

static long z = getTime(" -----z 父类静态变量赋值" + '\n');

static long getTime(String who) {
long time = System.currentTimeMillis();
System.out.print(time + who);
return time;
}

private static long c = getTime(" -----c 父类静态变量赋值" + '\n');
private final static long d = getTime(" -----d 父类静态变量赋值" + '\n');
}


用 IntelliJ IDEA 打开上述代码编译之后的 Father.class :

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.ak.torch.videoplayer.disassamble;

public class Father {
private static long a = getTime(" -----a 父类静态变量赋值\n");
private static final long b = getTime(" -----b 父类静态变量赋值\n");
static long z;
private static long c;
private static final long d;

Father() {
System.out.print(System.currentTimeMillis() + " ------ 父类代码块" + '\n');
System.out.print(System.currentTimeMillis() + " ------ 父类构造函数" + '\n');
}

static long getTime(String var0) {
long var1 = System.currentTimeMillis();
System.out.print(var1 + var0);
return var1;
}

static {
System.out.print(System.currentTimeMillis() + " ------ 父类静态代码块" + '\n');
z = getTime(" -----z 父类静态变量赋值\n");
c = getTime(" -----c 父类静态变量赋值\n");
d = getTime(" -----d 父类静态变量赋值\n");
}
}

总结一下:

一、静态代码块 与 静态变量赋值 在反编译成的代码中的位置

1、如果存在静态代码块,并且有静态变量的赋值写在了静态代码块的下面,那么这样的静态变量最终的赋值操作会被写在静态代码块中

2、如果存在多个静态代码块,最终会合并为一个

二、实例变量赋值 与 实例代码块 在反编译成的代码中的位置

1、如果存在实例代码块,并且有实例变量的赋值写在了实例代码块的下面,那么实例代码块中的操作和这样的实例变量最终的赋值操作会被写在构造函数中

2、实例代码块中的操作都会被写在构造函数中

3、如果存在多个实例代码块,都会被写在构造函数中

了解了变量赋值与代码块的执行顺序,就能从代码执行的角度解释有时候在代码块中使用变量时为什么会提示“非法前向引用(illegal forward reference)”

反编译工具 IntelliJ IDEA 的BUG

根据 静态代码块 与 静态变量赋值 的执行顺序取决于代码书写顺序这一原则,测试Father.java

class Father {

static {
bb = getTime(" ----bb 父类实例变量" + '\n');
}

static long aa = getTime(" ----aa 父类实例变量" + '\n');
static long bb;

static long getTime(String who) {
long time = System.currentTimeMillis();
System.out.print(time + who);
return time;
}
}
执行结果:

1515149709262 ----bb 父类实例变量
1515149709262 ----aa 父类实例变量
用 IntelliJ IDEA 打开上述代码编译之后的 Father.class

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.ak.torch.videoplayer.disassamble;

public class Father {
static long aa = getTime(" ----aa 父类实例变量\n");
static long bb = getTime(" ----bb 父类实例变量\n");

public Father() {
}

static long getTime(String var0) {
long var1 = System.currentTimeMillis();
System.out.print(var1 + var0);
return var1;
}
}
将此反编译之后的代码重新搞一个java文件,然后编译运行:

1515151279918 ----aa 父类实例变量
1515151279919 ----bb 父类实例变量

可见由IntelliJ IDEA反编译出的代码的执行顺序已经不是原来类该有的顺序了,IntelliJ IDEA反编译处理的 实例代码块
与 实例变量的执行顺序也有类似问题。

经过测试,用 JD-JUI 反编译也会有上述的问题,但是兼容性比 IntelliJ IDEA 好一些。

所以,这两款反编译工具反编译出来的代码并不能保证跟原来类的逻辑完全一致。

使用javap命令可以获取类初始化时代码的正确执行顺序。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐