注册 登录
编程论坛 JAVA论坛

求教,类的初始化顺序

zyhbecause 发布于 2018-03-21 11:28, 2568 次点击
class Root
 2 {
 3     static{
 4         System.out.println("Root的静态初始化块");
 5     }
 6     {
 7         System.out.println("Root的普通初始化块");
 8     }
 9     public Root()
10     {
11         System.out.println("Root的无参数的构造器");
12     }
13 }
14 class Mid extends Root
15 {
16     static{
17         System.out.println("Mid的静态初始化块");
18     }
19     {
20         System.out.println("Mid的普通初始化块");
21     }
22     public Mid()
23     {
24         System.out.println("Mid的无参数的构造器");
25     }
26     public Mid(String msg)
27     {
28         //通过this调用同一类中重载的构造器
29         this();
30         System.out.println("Mid的带参数构造器,其参数值:" + msg);
31     }
32 }
33 class Leaf extends Mid
34 {
35     static{
36         System.out.println("Leaf的静态初始化块");
37     }
38     {
39         System.out.println("Leaf的普通初始化块");
40     }   
41     public Leaf()
42     {
43         //通过super调用父类中有一个字符串参数的构造器
44         super("Java初始化顺序演示");
45         System.out.println("执行Leaf的构造器");
46     }
47
48 }
49
50 public class TestStaticInitializeBlock
51 {
52     public static void main(String[] args)
53     {
54         new Leaf();
55     }
56 }
请教执行结果
10 回复
#2
疯狂的小a2018-03-21 13:09
只有本站会员才能查看附件,请 登录
运行一下不就有结果了么.推荐用eclipse写代码
#3
zyhbecause2018-03-21 14:11
回复 2楼 疯狂的小a
为什么是这个顺序啊?,root的无参构造器运行完不应该是Mid的普通初始化-----Mid的无参构造器------Leaf的普通初始化---------(然后leaf的构造器里调用父类带参构造器)----------Mid的无参构造器------------Mid的带参构造器-------------Leaf构造器这个顺序么?
#4
zyhbecause2018-03-21 14:12
回复 2楼 疯狂的小a
最后四行我不理解,能详细解释下么?谢谢
#5
疯狂的小a2018-03-21 14:36
回复 4楼 zyhbecause
你这个,需要研究这么深的么?因为静态代码块是随着类的加载而加载,且只执行一次,所以毫无疑问,三个静态代码块先执行了.普通构造代码块随着对象的创建而加载,对象创建之前会先创建父类对象,父类对象创建时就执行了普通构造代码块和无参构造方法,但是当创建Leaf对象时调用了super("Java初始化顺序演示");,就是父类的有参构造,所以父类的有参构造会先执行,等执行完了,接着创建子类对象,就是Leaf对象,然后执行了他的普通构造代码块和无参构造方法.就是这样的.能看懂吗?
#6
九转星河2018-03-21 17:46
学C的路过,看看java~

看了上面大神的理解,我按自己的意思说一下~

其实可以这样理解,static静态代码是加载在内存块里面的,只有一个,因此里面的数据必须是常量,其在第一次加载类的时候而加载,并且只执行一次~

其实具体一点的就是编译程序的时候先从主函数加载Leaf,然后找到该类的构造方法的参数形式,例如这里先确定Left是无参构造方法,再找到它的父类构造方法是带参数的,父类Mid的带参构造方法是调用this即自身的无参构造方法,然后就找不到该类的父类Root构造方法,所以默认Root的构造方法是无参的,理论上需要保留各个类的的构造方法,具体过程应该是通过压栈处理,如果是第一次构建该类,那么顺便把静态代码块里面东西进行压栈~待知道Root的构造方法后再出栈执行构建方法流程,然而这些都是在javac也就是在预编译的字节码里面已经处理好了~
这可以在构造方法里面调用其它的构造方法必须写在第一行或者不写,不然不能通过编译器这一现象体现出来~
具体点在this();这行代码前面加个System.out.println();这样会不能通过编译~

值得注意的是Mid里面调用两个不同的构造方法但普通模块只执行一次,具体点来说就是每new一个Left类只执行一次~
所以可以确定虽然调用两次Left的构造方法,但内存里面只有一个Left,所以这体现出构造方法其实是一个特殊的方法,就是一个调用该类的初始化流程~真正的类构造应该是初始化写在普通代码块里面的东西~

虽然这个看上去有种很复杂的感觉,单是其实明白其中执行原理还是挺清晰的~

@上楼的大神,看看我说得对嘛~


[此贴子已经被作者于2018-3-21 17:51编辑过]

#7
疯狂的小a2018-03-21 17:57
如果非要从内存的角度分析....编译程序的时候先从主函数加载Leaf,然后找到该类的构造方法的参数形式,例如这里先确定Left是无参构造方法,再找到它的父类构造方法是带参数的...Mid也有无参构造...是不是逻辑混乱了
#8
九转星河2018-03-21 19:59
回复 7楼 疯狂的小a
所以初学不是很懂,纯属个人理解,那还是不管那么多好了,毕竟还是写点有实际意义的程序比较好
~
#9
疯狂的小a2018-03-21 20:41
回复 8楼 九转星河
其实个人觉得,对初学者而言,研究这种简单的代码还是很重要的.只是研究的时候能明白,时间长了就会忘记.所以初学者只要能理解这种思想就好了.知道代码是在内存中的什么位置.至于具体的流程,没有必要记住,况且对于复杂的代码而言,没有人能想明白.即使做框架的大师,也是想不明白的,所以意义不大.
#10
rind2018-03-22 01:04
程序代码:
package test;
public class Test1 {
    static {
        System.out.println("第一个静态代码块");
        // System.out.println(staticField);//编译不通过
    }
    private static String staticField = "静态域";
    static {
        System.out.println("第二个静态代码块");
        System.out.println("第二个静态代码块:" + staticField);
    }
    {
        System.out.println("第一个普通代码块");
        // System.out.println(firstField);//编译不通过
    }
    private String firstField = "第一个成员变量";
    {
        System.out.println("第二个普通代码块");
        System.out.println("第二个普通代码块:" + firstField);
        // System.out.println(secondField);//编译不通过
    }
    public Test1() {
        System.out.println("无参构造器");
        System.out.println("无参构造器:" + firstField);
        System.out.println("无参构造器:" + secondField);
    }
    private String secondField = "第二个成员变量";
}
程序代码:
package test;
public class Test {
    private static void test1() {
        Test1 t1;
    }

    private static void test2() {
        try {
            Class.forName("test.Test1");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    private static void test3() {
        new Test1();
    }

    public static void main(String[] args) {
        test1(); // 无效果
        System.out.println("--------------");
        test2();// 类加载
        System.out.println("++++++++++++++");
        test3();// 新建對象
    }
}
程序代码:
--------------
第一个静态代码块
第二个静态代码块
第二个静态代码块:静态域
++++++++++++++
第一个普通代码块
第二个普通代码块
第二个普通代码块:第一个成员变量
无参构造器
无参构造器:第一个成员变量
无参构造器:第二个成员变量
Java代码的执行顺序和数学里面的加减乘除的顺序规则差不多。
extends关键字翻译为“扩展”更贴切,也更容易理解类与类之间的关系,旧的比新的先执行,这样新的才有机会覆盖旧的。


[此贴子已经被作者于2018-3-22 01:08编辑过]

#11
有多难2018-03-26 20:19
new Leaf 实例的时候,先执行的static代码块,
从父类开始依次往下执行,所以是“root的静态初始化块”->“Mid的静态初始化块”->“Leaf的静态初始化块”;
然后执行普通代码块和构造方法,“root的普通初始化块”->“root的无参数构造器”->“Mid的普通初始化块”->“Mid的构造器
(执行到这一步,由于Mid类有两个构造器,new Leaf 实例的时候,Leaf的构造器显式调用了父类的带参数构造器并传入了参数“Java初始化顺序演示”,所以此处执行的是Mid的带参数构造器,
而Mid的带参数构造器又调用了Mid自身的无参数构造器,所以接下来的执行顺序如下)
->“Mid的无参数构造器”->“Mid的带参数构造器,其参数值:”->“Java初始化顺序演示”->“Leaf的普通初始化块”->“执行Leaf的构造器”


[此贴子已经被作者于2018-3-26 20:21编辑过]

1