【Java学习笔记系列】继承方式下静态成员变量、普通成员变量、静态代码块、构造代码块、构造函数在JVM的加载顺序

一、预先一下所需要的知识

Static关键字


首先我们来了解一下Static这个关键字。

  • Static可以用来修饰成员(属性和方法)
  • 被Static修饰的成员可以被所有该类的对象共享
  • 换句话说,被修饰的成员意味着是全局变量的意思。

代码证明:

    public class ClassC {
        static int a = 10;
        int b = 5;
        public void showA(String className){
            System.out.println(className+" a="+a+ " b="+b);
        }
        public void SetA(int a,int b){
            this.a = a;
            this.b = b;
        }
        //class1修改了a和b变量。
        public static void main(String[] args) {
            ClassC class1 = new ClassC();
            ClassC class2 = new ClassC();
            class1.showA("class1");
            class1.SetA(20,10);
            class1.showA("class1");
            class2.showA("class2");
        }
    }

结果是:

class1 a=10 b=5
class1 a=20 b=10
class2 a=20 b=5

上面我们可以看到,class1修改了a和b的变量,a是静态成员变量,b是普通成员变量。所以class2调用showA方法的时候,a显示的是class1所修改的值,b则还是classC类初始化时的值。这是因为静态成员将被同一个类的所有对象所共享,生命周期跟随类,而普通成员的生命周期只跟随类的具体的对象。

普通成员变量(实例变量)和静态成员变量(类变量)的区别?


1.生命周期不同
- 普通成员变量随着对象的创建而创建,随着对象的回收而释放
- 静态成员变量随着类的加载而创建,随着类的消失而消失
2.调用方式不同
- 普通成员变量只能够被具体的对象调用
- 静态成员变量可以被对象调用,也可以被类直接调用
3.数据存储位置不同
- 普通成员变量位于堆内存中
- 静态成员变量存储在方法区(数据区或则共享区)的静态区中

上面的代码可以证实

三种代码块的区别


1.静态代码块

在类中,方法体外定义,被static所修饰

public class ClassA {
    static {System.out.println("static code block");}
    }
  • 随着类的加载而执行
  • 只执行一次
  • 可用给类初始化,有的类不想使用构造方法去初始化

2.构造代码块

在类中,方法体外定义,未被static修饰

public class ClassA {
    {System.out.println("code block");}
    }
  • 在创建对象时执行,每创建一个对象就执行一次
  • 在创建对象时执行,在构造函数前执行
  • 可用于给对象初始化

3.普通代码块(局部代码块)

在方法体内定义的代码块

public class ClassA {
    public void show(){         
        {  System.out.println("局部代码块"); }  
    }  
}
  • 限定函数中的局部变量的生命周期

二、各成员在JVM的加载顺序

继承方式下各成员在JVM的加载顺序


ClassA.java

//父类
public class ClassA {

    //构造方法
    public ClassA() {
        System.out.println("ClassA - constructor method running");
    }

    //构造代码块
    {System.out.println("ClassA - code block running");}

    //静态代码块
    static {System.out.println("ClassA - static code block running");}

    //方法
    public void show() {
        System.out.println("ClassA - show method running!");
    }
}

ClassB.java

//子类
public class ClassB extends ClassA{

        //构造方法
        public ClassB() {
            System.out.println("ClassB - constructor method running");
        }

        //构造代码块
        {System.out.println("ClassB - code block running");}

        //静态代码块
        static {System.out.println("ClassB - static code block running");}

        //方法
        public void show() {
            System.out.println("ClassB - show method running!");
        }

        public static void main(String[] args) {
            ClassB classB = new ClassB();
            classB.show();
        }

}

结果:

ClassA - static code block running
ClassB - static code block running
ClassA - code block running
ClassA - constructor method running
ClassB - code block running
ClassB - constructor method running
ClassB - show method running!

分析:
主函数实例化了一个ClassB类的对象,又因为ClassB类继承于ClassA类,所以首先加载父类静态成员,再加载子类静态成员(成员变量、代码块)。当父子类静态成员都加载完毕之后,开始加载父类普通成员,加载父类构造函数。加载子类普通成员,加载子类构造函数。

注意:
- 当JVM在加载一个类的成员的时候,会将该类的成员放到构造函数里,原有逻辑之前,构造函数的原有逻辑最后执行。
- 类成员(成员变量、代码块)之间的执行顺序有实际代码顺序决定。
- 静态方法只能访问静态成员(非静态方法可以自由访问),下面会解释
– 静态方法不能使用this和super关键字,下面也会解释

结论:
1、初始化父类静态成员变量,静态代码块。顺序实际代码顺序决定
2、初始化子类静态成员变量,静态代码块。顺序实际代码顺序决定
3、初始化父类的成员变量,构造代码块。顺序实际代码顺序决定
4、初始化父类的构造函数。
5、初始化子类的成员变量,构造代码块。顺序实际代码顺序决定
6、初始化子类的构造函数。

* 成员变量和代码块是同级的,实际加载顺序由代码位置决定。*

其他要注意的要点


1.为什么静态方法只能访问静态成员,而不能访问普通成员?

  • 举个粟子,因为在类的加载中,静态成员优先于普通成员。所以如果在静态方法中访问普通成员变量。当JVM加载静态方法的时候,要访问的普通成员变量都还没有被JVM加载,根本找不到该变量。所以不能访问普通的成员。
  • 但是也有解决方法,那就是让该成员提前给JVM加载,通过new一个对象去调用普通成员,用于给静态方法访问。

Code Demo:

public class ClassA {
    int a = 5;
    static int b = 10;
    //构造方法
    public ClassA() {
        System.out.println("ClassA - constructor method running");
    }

    //构造代码块
    {System.out.println("ClassA - code block running");}

    //静态代码块
    static {
        ClassA classA = new ClassA();
        System.out.println( "ClassA - static code block running" + " a="+classA.a);
    }

    //方法
    public void show() {
        System.out.println("ClassA - show method running!");
    }

    public static void main(String[] args) {
        System.out.println("main method running!");
    }
}

结果:

ClassA - code block running
ClassA - constructor method running
ClassA - static code block running a=5
main method running!

分析:
为什么会执行了4条输出语句?重点是静态代码块中还有一个类的实例化。
因为这里的执行顺序是:
1.加载ClassA类的静态成员变量 b ;
2.加载ClassA类的静态代码块,在加载期间发现其中含有类的实例化,执行ClassA的构造方法,将ClassA类的成员放入原有构造函数逻辑之前。
3.加载ClassA类的成员变量 a
4.加载ClassA类的代码块
5.加载ClassA类的构造函数
6.输出ClassA类中静态代码块剩余的逻辑,然后ClassA类静态代码块加载完毕。
7.加载ClassA类主函数部分.
类的实例化对象让成员变量提前加载,所以才能够给静态代码块访问到。

2.为什么静态方法里面不能使用this,super关键字?

1)首先我们要区分一下类和对象的范畴
类是类,对象是对象,对象是一个类的实例化的结果。

2) 静态成员和普通成员内存的分配不同
静态成员是在类加载的时候就开始加载了,在内存中有块单独的静态内存区用于存储静态的成员,属于类的范畴,类的成员,是独一无二的。普通的成员是在类被实例化的过程中被JVM加载的,属于对象的范畴,相当于是一个副本成员,因为一个类是可以实例化很多个对象,每个对象都拥有属于自己的普通成员。

3)为什么不能使用this和super关键字呢?

  • 静态成员优先于对象的存在,在JVM加载中,静态成员优于主函数中对象的实例化,所以更早被JVM识别。
  • this代表该对象,引用的是当前对象,super也差不多,代表该对象的父类对象。

那么问题来了,在JVM加载某个类的时候,加载到某个静态方法,而该静态方法中含有this或则super的关键字,可是此时并没有任何实例化的对象存在,也就是此时还没有对象这个概念。那怎么能引用对象的成员或是引用对象的父类对象?所以静态方法中不能使用this和super关键字

参考网站:

[CSDN]-静态关键字static和静态代码块、局部代码快、构造代码块
[CSDN]-Java的静态变量,成员变量,静态代码块,构造块的加载顺序
[博客园]-关于构造代码块、局部代码块和静态代码块的作用和区别

猜你喜欢

转载自blog.csdn.net/snailmann/article/details/79850237