类的初始化顺序

类的初始化及初始化顺序

首先,类通过构造函数创建类的的实例并初始化,正常情况下,编译器会默认自动给类加一个无参构造函数(即使你不写),但是一旦你自己写了其它有参构造器,编译器就不会自动给你加无参构造器了,所以多数情况下,建议在写有参构造器时,再额外加写个无参构造器。

构造函数可以重载(方法名相同,参数列表的数据类型不同或者参数列表类型相同但顺序不同)。其次,构造函数内可以调用其它构造函数,但调用其它构造函数的代码必须放在最前面,而且最多只能调用一次其它构造函数

类中可能有代码块(静态和非静态)、属性(包括基本数据类型和引用数据类型(静态和非静态))、构造函数(构造函数只能是非静态)、方法(静态和非静态),甚至是内部类(静态和非静态)

在我们创建类的实例时必然涉及到初始化该类,这就需要将该类中的成员也一一初始化,这就涉及到了这些成员的初始化顺序的问题。

首先是静态成员,也就是类中被“static”关键字修饰的成员,这些成员属于类成员,它们的调用可直接通过“类名.成员”这种方式直接调用,而不需要借助类的实例化对象来调用,然后是非静态成员,也就是类中没有被“static”关键字修饰的成员,这些成员的调用需要借助类的实例化对象才能调用(即“类的实例化对象.成员”这种方式)

接下来我们看实例化对象时类中各个成员的初始化顺序

说之前,必须打破我们的思维固化,认为类的初始化是按代码出现的先后顺序初始化的,不说这是错的,这句话要成立是要有条件的,只有当类中的成员全都是静态的(或者全都是非静态的)情况下,才是按代码出现的先后顺序初始化的,这样说可能不是很严谨,因为有构造函数这个例外,下面会说。

如果静态成员和非静态成员夹杂在一起的话,就不是按代码出现的先后顺序来初始化成员了。实例化对象时,类中的静态成员的初始化要先于非静态成员,这个无关代码顺序,即使你把静态成员的代码写在非静态成员代码的后面,静态成员还是会先于非静态成员初始化

由于static关键字修饰的成员它并不依赖类的实例,所以不管你创建多少个类的实例,静态成员只会初始化一次,占据一份存储空间,该类的多个实例共享这一份静态成员。而对于非静态成员,由于他们依赖类的实例化对象,所以,你创建多少个实例出来,就会有多少份的非静态成员,各个实例的非静态成员独立,互不相关。

当涉及到的成员全都是静态成员(或者全都是非静态成员)时,成员的初始化顺序为:属性和代码块的初始化要先于构造函数,属性和代码块的初始化顺序就按代码出现的先后顺序来,谁先出现就先初始化谁(即先后顺序为:属性 = 代码块 > 构造函数,之后按代码出现的先后顺序进行初始化)

对属性进行初始化时,是有初始值的,如果该属性为基本数据类型,那初始值为各个数据类型对应的默认值(比如int为0,double为0.0等),如果该属性是引用数据类型,则初始值为null。初始化属性时会先将属性默认为该属性对应数据类型的默认值,然后才是进行“=”后的赋值,例如:

int a=20;
Person p=new Person("张三",24);

上面两句代码的赋值过程为:

对于基本数据类型来,比如上面的int类型的a,a的初始值为int的默认值0,进行“ = ”赋值后,这时候a的值才变为20。

对于应用数据类型,比如上面的Person类型的引用p,p的初始值为null,进行“=”赋值后,p才指向Person(“张三”,24)这个实例

总结,类的初始化顺序为:

1、静态成员 > 非静态成员

2、属性=代码块 > 构造函数,其它按代码出现的先后顺序进行初始化

先进行步骤1找出所有静态成员,然后将这些静态成员按步骤2的规则都一一初始化,初始化完静态成员后,接下来就开始初始化非静态成员,找出所有非静态成员,然后同样按步骤2的规则将这些非静态成员全部初始化

这里附上代码及运行结果,可根据上面的理论验证运行结果:

public class Test {
    static private Person p1=new Person(1);
    Test(){
        System.out.println("Test构造函数。。。。。。");
    }
    private Person p2=new Person(2);
    static{
        System.out.println("Test静态代码块。。。。。。");
    }

    {
        System.out.println("Test非静态代码块。。。。。。");
    }
    static private Person p3=new Person(3);
    public static void main(String[] args) {
        new Test();
    }
}

class Person{
    private int i;
    public Person(int i) {
        this.i=i;
        System.out.println("Person构造函数---------"+i);
    }

    {
        System.out.println("Person非静态代码块----------");
    }
    static{
        System.out.println("Person静态代码块----------");
    }
}
//运行结果
Person静态代码块----------
Person非静态代码块----------
Person构造函数---------1
Test静态代码块。。。。。。
Person非静态代码块----------
Person构造函数---------3
Person非静态代码块----------
Person构造函数---------2
Test非静态代码块。。。。。。
Test构造函数。。。。。。

涉及到内部类的初始化的顺序

内部类顾名思义即定义在类里面的类,内部类分为:非静态内部类、静态内部类、匿名内部类。内部类也是一个类,和它所在的外部类一样都是类,所以外部类要访问内部类中的成员(注意:包括静态成员)必须先创建内部类的实例,然后通过这个实例才能访问内部类中的成员。可能会问,内部类既然在外部类的里面,那初始化外部类时,内部类会不会被初始化呢?如果初始化外部类的过程中没有创建内部类的话,内部类是不会被初始化的。像下面这段代码:

public class Test {
    public static void main(String[] args) {
        Person p=new Person(10);//创建外部类实例
    }
}

class Person{
    private int i=3;
    private static String a="sdsd";
    private static void staticMethod(){
        System.out.println("外部类Person的静态方法.......");
    }

    static{
        System.out.println("外部类Person的静态代码块.......");
    }

    {
        System.out.println("外部类Person的非静态代码块.......");
    }

    public Person(int i) {
        this.i=i;
        System.out.println("外部类Person构造函数*********i="+i);
    }

    //非静态内部类:
    public class B{
        B(int i){
            System.out.println("非静态内部类B的构造函数************i= "+i);
        }

        {
            System.out.println("非静态内部类B的非静态代码块......");
        }
    }

    //静态内部类
    public static class A{
        A(int i){
            System.out.println("静态内部类A的构造函数************i= "+i);
        }

        {
            System.out.println("静态内部类A的非静态代码块......");
        }

        static{
            System.out.println("静态内部类A静态代码块......");
        }
    }
}
//运行结果:
外部类Person的静态代码块.......
外部类Person的非静态代码块.......
外部类Person构造函数*********i=10

从运行结果可以看出不管是静态还是非静态的内部类,只要外部类Person的初始化过程中没有创建内部类的话,内部类就不会被初始化。

静态内部类的初始化

创建静态内部类的实例时,初始化分为两种情况:

第一种:静态内部类中没有调用外部类的静态成员(静态内部类只能访问外部类的静态成员)或者引用外部类,这时很简单,初始化只涉及静态内部类本身,不涉及外部类。

public class Test {
    public static void main(String[] args) {
        Person.A A1=new Person.A(5);//创建静态内部类实例
    }
}

class Person{
    private int i=3;
    private static String a="sdsd";
    private static void staticMethod(){
        System.out.println("外部类Person的静态方法.......");
    }

    static{
        System.out.println("外部类Person的静态代码块.......");
    }

    {
        System.out.println("外部类Person的非静态代码块.......");
    }

    public Person(int i) {
        this.i=i;
        System.out.println("外部类Person构造函数*********i="+i);
    }

    //静态内部类
    public static class A{
        int i=2;
        A(int i){
            System.out.println("静态内部类A的构造函数************i= "+i);
        }

        {
            System.out.println("静态内部类A的非静态代码块......");
        }

        static{
            System.out.println("静态内部类A静态代码块......");
        }
    }
}
//运行结果:
静态内部类A静态代码块......
静态内部类A的非静态代码块......
静态内部类A的构造函数************i= 5

第二种,初始化静态内部类的过程中碰到了外部类的静态成员(因为静态内部类只能访问外部类的静态成员),这时就会涉及到外部类静态成员的初始化,什么时候碰到了外部类的静态成员,外部类的静态成员就会什么时候初始化,例如下面的代码中,静态内部类的静态代码块中调用了外部成员,这个时候在初始化静态内部类的静态代码块的过程中,就会开始初始化外部类的静态成员,注意:只要有一个外部类的(静态)成员被调用时,外部类的所有静态成员都会被初始化,而且只在第一次被调用时初始化一次,而外部类的非静态成员不会被初始化。

public class Test {
    public static void main(String[] args) {
        Person.A A1=new Person.A(5);//创建静态内部类实例
    }
}

class Person{
    private int i=3;
    private static String a="sdsd";
    private static void staticMethod(){
        System.out.println("外部类Person的静态方法.......");
    }

    static{
        System.out.println("外部类Person的静态代码块.......");
    }

    static{
        System.out.println("外部类的另一个静态代码块......");
        System.out.println("a="+a);
    }

    {
        System.out.println("外部类Person的非静态代码块.......");
    }

    //静态内部类
    public static class A{
        int i=2;
        A(int i){
            System.out.println("静态内部类A的构造函数************i= "+i);
        }

        {
            System.out.println("静态内部类A的非静态代码块......");
        }

        static{
            System.out.println("静态内部类A静态代码块......");
            staticMethod();//调用外部类的方法
        }
    }

    public Person(int i) {
        this.i=i;
        System.out.println("外部类Person构造函数*********i="+i);
    }
}
//运行结果:
静态内部类A静态代码块......
外部类Person的静态代码块.......
外部类的另一个静态代码块......
a=sdsd
外部类Person的静态方法.......
静态内部类A的非静态代码块......
静态内部类A的构造函数************i= 5

非静态内部类的初始化

非静态内部类:非静态内部类中不能有静态成员和静态代码块,该内部类可以访问外部类中的所有成员,非静态内部类的创建必须依赖外部类的实例对象。因为有先后顺序,先创建外部类,初始化外部类,再借助外部类的实例创建非静态内部类,初始化内部类,这个没什么好说的。

唯一可能要说的就是创建非静态内部类实例时的语法:

//其中B是Person的非静态内部类,两个类构造函数均带有一个int类型的参数
Person p=new Person(10);//创建外部类实例
Person.B B1=p.new B(6);//创建非静态内部类实例

涉及到继承关系时的初始化顺序

当有继承关系时,初始化顺序为:父类静态代码块(静态属性) > 子类静态代码块(静态属性) > 父类非静态代码块(非静态属性)> 父类构造函数 > 子类非静态代码块(非静态属性) > 子类构造函数。当代码块和属性都是静态或非静态的情况下谁先出现谁就先初始化。

public class Father{
    private String name="Father";
    public static int age=38;

    public Father(String name,int age) {
        this.name=name;
        this.age=age;
        System.out.println("Father的构造函数......name="+name+"  "+"age="+age);
    }

    static {
        System.out.println("Father的静态代码块......age="+age);
    }

    {
        System.out.println("Father的非静态代码块......name="+name);
    }

    public static void main(String[] args) {
        Son son=new Son("张三",20);
    }
}

class Son extends Father{
    public Son(String name,int age) {
        super(name,age);
        this.name=name;
        this.age=age;
        System.out.println("Son的构造函数......name="+name+"  "+"age="+age);
    }

    private String name="Son";
    public static int age=18;

    static {
        System.out.println("Son的静态代码块......age="+age);
    }

    {
        System.out.println("Son的非静态代码块......name="+name);
    }

 }
//运行结果:
Father的静态代码块......age=38
Son的静态代码块......age=18
Father的非静态代码块......name=Father
Father的构造函数......name=张三  age=20
Son的非静态代码块......name=Son
Son的构造函数......name=张三  age=20

对运行结果分析:这其实又回到了本文最上面说的类的初始化顺序,因为new Son(“张三”,20);这句代码是new的Son对象,毫无疑问会调用Son的构造函数,子类Son构造函数如下:

public Son(String name,int age) {
    super(name,age);//调用父类构造函数
    this.name=name;
    this.age=age;
    System.out.println("Son的构造函数......name="+name+"  "+"age="+age);
}

而Son的构造函数里面最前面一行代码调用的是父类Father的构造函数,父类构造函数如下:

public Father(String name,int age) {
    this.name=name;
    this.age=age;
    System.out.println("Father的构造函数......name="+name+"  "+"age="+age);
}

上面调用了父类构造函数,就涉及到父类的初始化,这时,进入到父类中,父类中的name和age属性,定义的时候我们给了指定值,分别是“Father”和“38”,

private String name="Father";
public static int age=38;

而输出中结果中却变成了“张三”和“20”,这明明是给Son的值,这是因为相比代码块和属性,构造函数是最后执行的原因,构造函数执行之前,可以看出,打印出来父类的name和age就是我们指定的“Father”和“38”,而执行构造函数时,因为构造函数的参数就是name和age,所以当执行new Son(“张三”,20);时,传给Son构造函数的name和age的值分别是“张三”和“20”,所以父类的构造函数接收的参数也是这两个,然后通过父类构造函数将中的this.name=name;和this.age=age;这两句代码,将之前指定的值“Father”和“38”给覆盖了,因此父类Father中name和age属性的值由“Father”和“38”变成了“张三”和“20”,执行完父类的构造函数后继续往下执行,开始调用子类构造函数中的this.name=name;和this.age=age;这两句代码,将子类Son中的name和age的值由原来指定的“Son”和“18”覆盖,变成了最后的“张三”和“20”。

这就是为什么子类中属性的值会覆盖父类的同名属性。

至于为啥子类的静态代码块(静态属性)会先于父类的非静态代码块(静态属性)执行,这方面可以看看Java虚拟机加载和初始化类这方面的内容。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值