java提高之静态代码块、非静态代码块、静态方法、构造方法、构造代码块

有过java基础的同学肯定绕不开这个小山坡,静态代码块、非静态代码块、静态方法、构造方法、构造代码块,这些,哪些先执行,哪些后执行,为什么呢?

首先,先说下论点,再用code佐证,最后探讨机制。

java类加载顺序

1、虚拟机在首次加载Java类时,会对静态初始化块、静态成员变量、静态方法进行一次初始化 2、只有在调用new方法时才会创建类的实例 3、类实例创建过程:按照父子继承关系进行初始化,首先执行父类的初始化块部分,然后是父类的构造方法;再执行本类继承的子类的初始化块,最后是子类的构造方法 4、类实例销毁时候,首先销毁子类部分,再销毁父类部分

非静态代码块是在每次实例化对象时被调用的。new对象之后发生的事情:给对象的实例变量(非“常量”)分配内存空间,默认初始化成员变量;成员变量声明时的初始化;初始化块初始化(又称为构造代码块或非静态代码块);构造方法初始化。

示例代码

public class Test01 {
    public static void main(String[] args) {
        B b = new B();
    }
}
class B extends A{
    static{
        System.out.println("子类的静态代码块");
    }
    C c = new C();
    {
        System.out.println("子类的构造代码块");
        C.method();
    }
    B(){
        System.out.println("子类的构造方法");
    }
}
class A{
    static{
        System.out.println("父类的静态代码块");
    }
    C c = new C();
    A(){
        System.out.println("父类的构造方法");
    }
}
class C{
    static public  void method(){
        System.out.println("C的静态方法");
    }
    static{
        System.out.println("C类的静态代码块");
    }
    C(){
        System.out.println("C的构造方法");
    }
}

代码运行的结果:

父类的静态代码块
子类的静态代码块
C类的静态代码块
C的构造方法
父类的构造方法
C的构造方法
子类的构造代码块
C的静态方法
子类的构造方法

解读:
首次加载Java类时,会对静态初始化块、静态成员变量、静态方法进行一次初始化,而B继承A,本来要先执行B的静态代码块,但按照父子关系初始化,A加了个塞,先执行A的静态代码块,打印“父类的静态代码块”;B的静态代码块也加载,打印“子类的静态代码块”;在父类中把C类也加载进来了,静态代码块执行,打印“C类的静态代码块”,C在new的时候,构造方法执行,打印“C的构造方法”;然后执行到A的构造方法,打印“父类的构造方法”;接着在B类继续向下执行,又new了个C,打印“C的构造方法”,然后向下执行非静态代码块,打印“子类的构造代码块”以及“C的静态方法”;最后B类的构造方法执行,打印“子类的构造方法”。

机制

具体内存中如何加载,暂时还没时间看,TODO,等看到java的类加载机制再详细探讨。

根据反编译的结果来看,整个运行过程是这样的:

public class Test01 {
    public static void main(String[] args) {
        B b = new B();
    }
}
class B extends A{
    static{
        System.out.println("子类的静态");
    }
    C c ;

    B(){
        super();
        c = new C();
        System.out.println("子类的构造代码块");
        C.method();
        System.out.println("子类的构造方法");
    }
    public void method(){
        System.out.println("子类的成员方法");
    }
}
class C{
    static public  void method(){
        System.out.println("C的静态方法");
    }
    static{
        System.out.println("C类的静态");
    }
    C(){
        super();

        System.out.println("C的构造方法");
    }
}
class A{
    static{
        System.out.println("父类的静态");
    }
    C c ;
    A(){
        super();
        c = new C();
        System.out.println("父类的构造方法");
    }
}

构造方法里面,先super(),再成员变量(c=new C()),再其他代码,操作完毕,依次读取。这就是内存中真实的运行规则。

扩展

看下面的题,请问输出的是什么

public class Test03 {
    public static void main(String[] args) {
        Zi zi = new Zi();
    }
}
class Zi extends Fu{
    public int count=3;
    public Zi(){
    }
    public void print(){
        System.out.println("Zi..."+count);
    }
}
class Fu{
    public int count=10;
    public Fu(){
        print();
    }
    public void print(){
        System.out.println("Fu..."+count);
    }
}

同之前的逻辑,改造后代码如下:

public class Test03 {
    public static void main(String[] args) {
        Zi zi = new Zi();
    }
}
class Zi extends Fu{
    public int count=0;//默认初始值为0
    public Zi(){
        super();
        count=3;
    }
    public void print(){
        System.out.println("Zi..."+count);
    }
}
class Fu{
    public int count=0;
    public Fu(){
        super();
        count=10;
        print();
    }
    public void print(){
        System.out.println("Fu..."+count);
    }
}

Zi zi = new Zi(),过去找子类的构造方法,super(),此时子类的count还是0,找父类的构造方法,父类,的count改为10,print(),子类自己有重写,找子类自己的,count也是找子类自己的,此时还是0,结果Zi...0

真实JVM中运行情况

类加载过程分为:加载、验证、准备、解析、初始化这几个阶段。其中准备阶段类变量(static修饰的变量)分配内存并设置类变量的初始值一般为零值,final修饰的直接赋值);初始化阶段是执行类构造器<clinit>()方法的过程,包括类中所有类变量的赋值动作静态代码块。虚拟机保证子类<clinit>()之前,父类该方法执行完毕。父类静态代码块优先于子类变量赋值动作。实例化过程一般都清楚。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值