xml地图|网站地图|网站标签 [设为首页] [加入收藏]

您的位置:亚洲必赢 > 计算机尝试 > Java程序初始化的顺序,Java类初始化顺序

Java程序初始化的顺序,Java类初始化顺序

发布时间:2019-05-30 05:11编辑:计算机尝试浏览(120)

      之前的一篇博客里我写了关于在一个类中的程序初始化顺序,但是在Java的面向对象里,类之间还存在着继承的关系。所以关于程序的初始化顺序,我们可以再细划分为:父类静态变量,父类的静态代码块,父类构造器,父类非静态变量,父类非静态代码块,子类静态变量,子类静态代码块,子类构造器,子类非静态成员变量和子类非静态代码块。
    本篇博客我们讨论的就是关于程序初始化的过程中,上述的成员在初始化加载先后顺序。
    
      在此前我们讨论得出的结论:在一个类中,Java程序加载的顺序是:静态变量-->静态代码块-->非静态变量-->非静态代码块-->构造器.
    
      父类的代码:
    
    public class SuperClass {
        //父类与子类都在一个包中,这里我们就使用default修饰符
        //这是一个父类的静态变量,此时还是初始化的默认值null
        static String superStaticVariale;
    
        // 静态代码块,给String赋值
        static {
            superStaticVariale = "父类静态代码块赋值成功";
            System.out.println("此时运行的是父类的静态代码块:" superStaticVariale);
        }
    
        // 无参构造,覆盖静态代码块的值
        SuperClass(){
            superStaticVariale = "父类构造器赋值成功";
            System.out.println("此时运行的是父类的构造器:" superStaticVariale);
        }
    
        //定义一个非静态变量
        String superVariale;
    
        // 定义一个非静态代码块
        {
            superVariale = "父类非静态代码块赋值";
            System.out.println("此时运行的是父类的非静态代码块:" superVariale);
        }
    }
    
      子类的代码:
    
    public class SubClass extends SuperClass{
        static String subStaticVariale;
    
        // 静态代码块,给String赋值
        static {
            subStaticVariale = "子类静态代码块赋值成功";
            System.out.println("此时运行的是子类的静态代码块:" subStaticVariale);
        }
    
        // 无参构造,覆盖静态代码块的值
        SubClass(){
            superStaticVariale = "子类构造器赋值成功";
            System.out.println("此时运行的是子类的构造器:" superStaticVariale);
        }
    
        //定义一个非静态变量
        String subVariale;
    
        // 定义一个非静态代码块
        {
            subVariale = "子类非静态代码块赋值";
            System.out.println("此时运行的是子类非静态代码块:" subVariale);
        }
    }
    
      测试代码:
    
    public class Main {
    
        public static void main(String[] args) {
            SubClass s = new SubClass();
        }
    }
    
      运行结果:
    ```
    此时运行的是父类的静态代码块:父类静态代码块赋值成功
    此时运行的是子类的静态代码块:子类静态代码块赋值成功
    此时运行的是父类的非静态代码块:父类非静态代码块赋值
    此时运行的是父类的构造器:父类构造器赋值成功
    此时运行的是子类非静态代码块:子类非静态代码块赋值
    此时运行的是子类的构造器:子类构造器赋值成功
    ```
      很显然,在继承关系中,代码的加载顺序是:父类的静态变量-->父类的静态代码块-->子类静态变量-->子类的静态代码块-->父类非静态变量-->父类的非静态代码块-->父类的构造器-->子类非静态变量-->子类非静态代码块-->子类构造器
    
      进一步测试:
    
    public class Main {
    
        public static void main(String[] args) {
            SubClass s = new SubClass();
            SubClass s1 = new SubClass();
            SubClass s2 = new SubClass();
        }
    }
    
    运行结果:
    ```
    此时运行的是父类的静态代码块:父类静态代码块赋值成功
    此时运行的是子类的静态代码块:子类静态代码块赋值成功
    此时运行的是父类的非静态代码块:父类非静态代码块赋值
    此时运行的是父类的构造器:父类构造器赋值成功
    此时运行的是子类非静态代码块:子类非静态代码块赋值
    此时运行的是子类的构造器:子类构造器赋值成功
    此时运行的是父类的非静态代码块:父类非静态代码块赋值
    此时运行的是父类的构造器:父类构造器赋值成功
    此时运行的是子类非静态代码块:子类非静态代码块赋值
    此时运行的是子类的构造器:子类构造器赋值成功
    此时运行的是父类的非静态代码块:父类非静态代码块赋值
    此时运行的是父类的构造器:父类构造器赋值成功
    此时运行的是子类非静态代码块:子类非静态代码块赋值
    此时运行的是子类的构造器:子类构造器赋值成功
    ```
      得出结论:
      父类与子类的静态代码都只执行一次,然后非静态代码块与构造器是组合出现的。
    
      简化一下代码:
    
    public class Main {
    
        public static void main(String[] args) {
       C c= new C();
        }
    }
    
    class A{
        A(){
            System.out.println("A的无参构造器");
        }
    }
    
    class B extends A{
    //    B(int a){
        B(){
            System.out.println("B的无参构造器");
        }
    }
    
    class C extends B{
        C(){
            System.out.println("C的无参构造器");
        }
    }
    
      运行结果:
    ```text
    A的无参构造器
    B的无参构造器
    C的无参构造器
    ```
      调用C的构造器生成C的实例对象会从最上级的父类的无参构造器开始逐层调用,那么我们的类都继承了一个超级父类Object,也就是在我们最初的错误代码中,我们调用Student的无参构造创建一个对象时,首先会调用这个对象的父类Object的无参构造器,
    
    class Student{
       String name;
    
       {
          name = "老大";
       }
    
       Student(){
           this(name);//这样会报错
          super();
          System.out.println("题目要求写一个无参的构造器");
       }
    
       Student(String name){
          this.name = name;
          System.out.println(name);
       }
    
    }
    
      子类实例化默认调用父类的无参构造器,也就是如上this调用在super()之前(实际中这两者不会同时出现),name此时是非静态属性,此时会报错错误: 无法在调用超类型构造器之前引用name。
    
    class Student{
       static String name;
    
       {
          name = "老大";
       }
    
       Student(){
           this(name);
          System.out.println("题目要求写一个无参的构造器");
       }
    
       Student(String name){
          this.name = name;
          System.out.println(name);
       }
    
    }
    
      当name是静态属性时,代码块是非静态时,编译通过,调用子类的无参构造器时this(name),输出结果是:
    ```text
    null
    题目要求写一个无参的构造器
    ```
    此时的this()调用实参构造并没有赋值成功。
    
    class Student{
       static String name;
    
       static{
          name = "老大";
       }
    
       Student(){
           this(name);
          System.out.println("题目要求写一个无参的构造器");
       }
    
       Student(String name){
          this.name = name;
          System.out.println(name);
       }
    }
    
      此时运行结果:
    ```text
    老大
    题目要求写一个无参的构造器
    ```
      这样赋值成功。由此证明我们的结论是正确的,this()是在子类父类构造器之前进行的操作super(),当子类代码块是非静态时,子类非静态代码块会在执行父类构造器之后执行,所以this(name)时name还没有被赋值,所以打印是null。
    
      结论:
      1. 一个类中可以在无参构造器中调用此类的有参构造器(顺序反过来);
      2. 在执行子类的无参构造器时会默认调用最高级父类无参构造,并逐级调用直至子类的无参构造;
      3. Java程序的加载顺为父类的静态变量-->父类的静态代码块-->子类静态变量-->子类的静态代码块-->父类非静态变量-->父类的非静态代码块-->父类的构造器-->子类非静态变量-->子类非静态代码块-->子类构造器,且静态变量或代码块无论构造器调用多少次,他只会执行一次,后面再调用构造器则会执行非静态属性及代码块构造器。
    
      最后关于为什么子类会调用父类的构造器,这个从设计着的角度来看是为了给从父类继承的属性初始化,子类需要知道父类是如何给属性初始化的。
    

    Java程序初始化的顺序,Java类初始化顺序。Java程序初叶化遵从原则:

    今天在写构造器方法的时候,遇到了一个小问题,由这个问题引发了一连串的思考,在一个Java类中变量与类的初始化执行顺序是什么样的呢?
    ## 发现问题
    
    class Student{
       private String name;
    
       void setName(String name){
          this.name = name;
       }
       String getName(){
          return name;
       }
    
       Student(){
          //this(this.name);
          this(name);
          System.out.println("题目要求写一个无参的构造器");
       }
    
       Student(String name){
          this.name = name;
       }
    
    }
    class TestStudent{
       public static void main(String[] args){
          Student stu1 = new Student();
          System.out.print(stu1.getName());
          Student stu2 = new Student("老大");
          System.out.println(stu2.getName());
       }
    }
    
    此时会报错:无法在调用超类型构造器之前引用name.
    
    在使用构造器创建对象时,此时的成员变量name的值是否已经完成初始化,无参构造中调用它时报的这个错意味着什么。我们本篇博客就来讨论一下,一个类创建对象时到底做了哪些事?
    
    ## 思考问题
    首先,对于一个类来说加载分为五个部分,分别是静态变量,静态代码块,非静态变量,非静态代码块以及构造器。
    
    ### 单个类成员加载顺序
    测试代码:
    
    class Student{
        // 静态变量
        static String name;
    
        // 静态代码块
        static{
            System.out.println("刚运行到静态代码块时的静态变量值:" name);
            name = "静态name值";
            System.out.println("静态代码块结束时的静态变量值:" name);
        }
    
        //定义一个无参构造器
        Student(){
            System.out.println("刚运行到构造器时的静态变量值:" name);
            name = "这是一个无参的构造器";
            System.out.println("构造器结束时的静态变量值:" name);
        }
        //定义一个非静态变量
        String name2;
    
        //定义一个非静态代码块
        {
            System.out.println("刚运行到非静态代码块时的非静态变量值:" name2);
            name2 = "非静态name值";
            System.out.println("非静态代码块结束时的非静态变量值:" name2);
        }
    }
    
    class TestStudent{
        public static void main(String[] args){
            Student stu = new Student();
        }
    }
    
    此时编译代码执行的结果是:
    ```
    刚运行到静态代码块时的静态变量值:null
    静态代码块结束时的静态变量值:静态name值
    刚运行到非静态代码块时的非静态变量值:null
    非静态代码块结束时的非静态变量值:非静态name值
    刚运行到构造器时的静态变量值:静态name值
    构造器结束时的静态变量值:这是一个无参的构造器
    ```
    由此可以看出,当我们声明的类成员变量是一个静态成员变量的时候,在调用构造器之前,我们的静态成员变量已经生成并初始化成相应的数据类型的默认值(即此处String对象的默认值位null)。
    然后在静态代码块中,我们将静态变量赋值,然后程序跳转到非静态变量声明与赋值。再执行非静态代码块,最后直行到程序的无参构造器。
    
    所以,通过此程序代码,我们得出结论:单个类的程序加载顺序是:静态变量-->静态代码块-->非静态变量-->非静态代码块-->构造器。
    
    也就是说调用构造器时,静态与非静态的属性都已经完成初始化工作了,this(name)调用报错与name属性本身没有关系。
    
    ### 深入思考类加载顺序
    既然说到加载顺序,那么我们继续完成类成员的加载顺序。关于变量与代码块之间的关系,或者说根据我们上面的这段代码得出这个初步的结论我们还有待商榷,因为,我们的程序加载的顺序是自上而下的,也就是说,我们的得到的这个结论有可能是因为我们习惯性的排版导致的,我们声明各部分的顺序偶可能影响我们得出的结论。为了确定我们程序的严谨性,我们需要进一步的调整代码的顺序,来加强验证我们代码实验的逻辑严谨性。
    
    public class Student2 {
        // 静态代码块放到前面,此时name还未声明,所以会报错
        static{
            System.out.println("刚运行到静态代码块时的静态变量值:" name);
            name = "静态name值";
            System.out.println("静态代码块结束时的静态变量值:" name);
        }
    
        // 静态变量
        static String name;
    
    
        //定义一个无参构造器
        Student2(){
            System.out.println("刚运行到构造器时的静态变量值:" name);
            name = "这是一个无参的构造器";
            System.out.println("构造器结束时的静态变量值:" name);
        }
        //定义一个非静态代码块
        {
            System.out.println("刚运行到非静态代码块时的非静态变量值:" name2);
            name2 = "非静态name值";
            System.out.println("非静态代码块结束时的非静态变量值:" name2);
        }
        //定义一个非静态变量
        String name2;
    }
    
    class TestStudent{
        public static void main(String[] args){
            Student stu = new Student();
            Student2 stu2 = new Student2();
        }
    }
    
    上面代码运行的结果:
    ```
    Error:(6, 48) java: 非法前向引用
    ```
    此时将代码块拿到变量声明的前面我们的代码出现了错误提示,这说明了我们一开始得到的结论并不严谨,我们这里可以得出代码块的执行是在变量声明之前的。
    所以,我们可以根据常识大胆的猜想,单个类程序加载的顺序是静态-->非静态-->构造器,其中变量声明与代码块的执行顺序与代码前后位置有关,并没有严格的前后之分,程序员将代码写在前边的的先执行。
    
    ### 验证猜想
    
    public class Student3 {
        //定义一个无参构造器
        Student3(){
            System.out.println("刚运行到构造器时的静态变量值:" name);
            name = "这是一个无参的构造器";
            System.out.println("构造器结束时的静态变量值:" name);
        }
    
        //定义一个非静态代码块
        {
            name2 = "非静态name值";
            System.out.println("非静态代码块结束时的静态变量值:" name);
        }
        //定义一个非静态变量
        String name2;
    
        // 静态代码块
        static{
            System.out.println("运行到静态代码块");
    //        name = "静态代码块里赋的值";
        }
    
        // 静态变量
        static String name;
    }
    
    class TestStudent{
        public static void main(String[] args){
            Student3 stu3 = new Student3();
        }
    }
    
    上面代码执行的结果:
    ```
    运行到静态代码块
    非静态代码块结束时的静态变量值:null
    刚运行到构造器时的静态变量值:null
    构造器结束时的静态变量值:这是一个无参的构造器
    ```
    基本验证了我们的猜想是正确的,但是在结尾我又做了一个有趣的测试。
    
    ### 测试
    
    public class Student3 {
        //定义一个无参构造器
        Student3(){
            System.out.println("刚运行到构造器时的静态变量值:" name);
            name = "这是一个无参的构造器";
            System.out.println("构造器结束时的静态变量值:" name);
        }
    
        //定义一个非静态代码块
        {
            name2 = "非静态name值";
            System.out.println("非静态代码块结束时的静态变量值:" name);
        }
        //定义一个非静态变量
        String name2;
    
        // 静态代码块
        static{
            System.out.println("运行到静态代码块");
            name = "静态代码块里赋的值";// 按道理说,我们这里没有声明就直接赋值操作了
        }
    
        // 静态变量
        static String name;
    }
    
    class TestStudent{
        public static void main(String[] args){
            Student3 stu3 = new Student3();
        }
    }
    
    上面代码执行的结果:
    ```
    运行到静态代码块
    非静态代码块结束时的静态变量值:静态代码块里赋的值
    刚运行到构造器时的静态变量值:静态代码块里赋的值
    构造器结束时的静态变量值:这是一个无参的构造器
    ```
    也就是说在静态代码块里,我们无法引用后面的静态变量,但是我们编译之前可以对他进行赋值,并且在后面的非静态代码块里我们还可以取到里面的值,再次做出假设,这是java虚拟机在编译时不让向前引用,此时的变量其实已经完成了声明初始化等一系列操作(都是存在方法区),只是通过不了编译而已。
    所以我认为,我们最早得到的结论应该才是正确的Java程序整个加载流程的顺序。
    
    这中间也可能时JDK和IDE一起努力做了点什么,但是实际上也不影响,我们的代码块的存在本来就是为了完成变量的初始化工作的,所以将代码块放到属性声明之前是毫无意义的操作,所以这里只是遇到了测试一下而已,实际操作中毫无意义。
    
    ## 得出结论
    总结:在一个类中,初始化顺序为:
    1. 静态变量,静态变量初始化;
    2. 静态代码块;
    3. 非静态变量初始化;
    4. 非静态代码块;
    5. 构造器。
    
    ##最后
    到这里,我们理清楚了单个类中的各部分的加载顺序,但是我们文章一开始提到的问题并没有解决,如果构造器执行是在静态和非静态属性及代码块之后的话,此时的成员变量应当已经有了初始化值了,再不济成员变量还有一个初始的null值,但是这里报了无法在调用超类型构造器之前引用name。
    这说明这里调用name关系到了这个类的父类构造器,所以我们后面继续探讨类加载在继承中的加载顺序,就可以解决这个问题了。
    

    初始记录从前,笔者想先上一张图片,这是书本上给出的代码示例,不知道大家的率先深感是什么,反正小编先是次探望这段代码的时候心境是挺复杂的。由于当下的作者对类开首化流程还会有个别熟习,所以根本不能想象代码是怎么运行的。

    一.静态对象(变量)优先于非静态对象(变量)伊始化。在那之中,静态对象(变量)只起先化三遍,而非静态对象(变量)或许会伊始化数次。

    图片 1

    二.父类优先于子类实行初叶化。

    截取自《Java编制程序观念》

    三.如约成员变量的概念顺序进行早先化。固然变量定义散布于方法定义之间,它们依旧会在其它措施(包含构造器)被调用以前获得开头化。即:先变量后措施。 

    理所当然,假设精晓了加载顺序的话,不管代码多乱,获得周转结果也就轻便了。

     

    图片 2

    具体试行顺序如下:

    image.png

    本文由亚洲必赢发布于计算机尝试,转载请注明出处:Java程序初始化的顺序,Java类初始化顺序

    关键词: 随笔 必赢娱乐网址 Java编程思...

上一篇:validator校验方式

下一篇:没有了