zoukankan      html  css  js  c++  java
  • 九、 Java程序初始化的顺序(二)

      之前的一篇博客里我写了关于在一个类中的程序初始化顺序,但是在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程序的加载顺为父类的静态变量-->父类的静态代码块-->子类静态变量-->子类的静态代码块-->父类非静态变量-->父类的非静态代码块-->父类的构造器-->子类非静态变量-->子类非静态代码块-->子类构造器,且静态变量或代码块无论构造器调用多少次,他只会执行一次,后面再调用构造器则会执行非静态属性及代码块构造器。

      最后关于为什么子类会调用父类的构造器,这个从设计着的角度来看是为了给从父类继承的属性初始化,子类需要知道父类是如何给属性初始化的。
  • 相关阅读:
    TXLSReadWriteII 公式计算
    Delphi TXLSReadWriteII2 带的demo中直接编辑XLS文件的例子
    delphi图片欣赏
    SQL 读取csv 文件批量插入数据
    Delphi TXLSReadWriteII 导出EXCEL
    Rollup 与 webpack的区别
    ref 属性使用eslint报错
    内容超出省略实现
    mac 环境配置
    前端学习资料整理
  • 原文地址:https://www.cnblogs.com/Jeffding/p/9490510.html
Copyright © 2011-2022 走看看