目录结构:
一、预先一下所需要的知识
二、各成员在JVM的加载顺序
首先我们来了解一下Static
这个关键字。
详见我的另一篇博文:
【Java修饰符之三】Java中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("局部代码块"); }
}
}
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类,所以首先加载父类静态成员,再加载子类静态成员(成员变量、代码块)。当父子类静态成员都加载完毕之后,开始加载父类普通成员,加载父类构造函数。加载子类普通成员,加载子类构造函数。
注意:
结论:
1、 初始化父类静态成员变量,静态代码块。顺序实际代码顺序决定
2、 初始化子类静态成员变量,静态代码块。顺序实际代码顺序决定
3、 初始化父类的成员变量,构造代码块。顺序实际代码顺序决定
4、 初始化父类的构造函数。
5、 初始化子类的成员变量,构造代码块。顺序实际代码顺序决定
6、 初始化子类的构造函数。
成员变量和代码块是同级的,实际加载顺序由代码位置决定
1.为什么静态方法只能访问静态成员,而不能访问普通成员?
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加载某个类的时候,加载到某个静态方法,而该静态方法中含有this或则super的关键字,可是此时并没有任何实例化的对象存在,也就是此时还没有对象这个概念。那怎么能引用对象的成员或是引用对象的父类对象?所以静态方法中不能使用this和super关键字