Java面向对象之类加载与static关键字
静态成员变量,静态块,静态成员方法
public class MyClass {// 静态字段(静态成员变量)static int staticField1 = 10; // 初始化表达式static int staticField2; // 默认初始值是0// 静态代码块static {staticField2 = 20;System.out.println("Static block executed");}// 构造方法public MyClass() {System.out.println("Constructor called");}//静态成员方法public static void printMessage() { System.out.println("Hello from static method!"); }public static void main(String[] args) {MyClass obj = new MyClass(); // 创建类的实例}
}
类加载过程
在Java中,类的加载是由Java虚拟机(JVM)的类加载器执行的,并且这个过程是自动的。以下是类加载过程中静态字段的加载和初始化的详细步骤:
- 加载(Loading):
- 类加载器读取
.class文件,并将其数据转换成方法区中的数据结构,同时在堆中生成一个对应的java.lang.Class对象作为访问方法区数据的入口。
- 类加载器读取
- 链接(Linking):
- 验证(Verification): 确保加载的类信息符合JVM规范,没有安全方面的问题。
- 准备(Preparation): 为类中的所有静态字段分配内存,并设置默认初始值(对于基本类型,这通常是0、false或null;对于对象引用,则是null)。这个阶段并不会执行任何代码,也不会执行静态字段的初始化表达式。
- 解析(Resolution): 将类、接口、字段和方法的符号引用转换为直接引用。
- 初始化(Initialization):
- 这个阶段是执行类构造器
<clinit>()方法的过程,该方法是由编译器自动收集类中的所有静态字段的赋值动作和静态代码块中的语句合并产生的。在这个阶段,JVM会根据程序中的赋值语句对静态字段进行初始化。 - 静态字段的初始化顺序与它们在源代码中的声明顺序相同。
- 如果静态字段是final的,并且它的值在编译时已经确定(即编译时常量),那么它会在准备阶段被初始化,而不是在初始化阶段。
以下是一个示例,说明静态字段的加载和初始化过程:
- 这个阶段是执行类构造器
public class MyClass {// 静态字段static int staticField1 = 10; // 初始化表达式static int staticField2; // 默认初始值是0// 静态代码块static {staticField2 = 20;System.out.println("Static block executed");}// 构造方法public MyClass() {System.out.println("Constructor called");}public static void main(String[] args) {MyClass obj = new MyClass(); // 创建类的实例}
}
在这个例子中,当MyClass类被加载时,以下步骤发生:
- 在准备阶段,
staticField1和staticField2都会在方法区分配内存,并设置默认初始值(staticField1在准备阶段就会被赋值为10,因为它是编译时常量)。 - 在初始化阶段,执行静态代码块,
staticField2被赋值为20,并且打印出"Static block executed"。 - 当创建
MyClass的实例时,构造方法会被调用,打印出"Constructor called"。
这个过程确保了静态字段的正确加载和初始化,并且只发生一次,即在类首次被加载时。
另外需要注意的点是:在Java中,一个类通常只在它被使用时才会被加载。具体来说,类会在以下情况下被加载:
-
创建类的实例:当你使用
new关键字创建一个类的实例时,JVM会加载这个类。 -
访问类的静态字段或方法:当你访问一个类的静态字段或调用静态方法时,JVM会加载这个类。
-
初始化子类:如果你初始化一个子类(即使是静态初始化),并且它的父类还没有被加载和初始化,那么JVM会先加载和初始化父类。
-
反射:如果你使用Java反射API来访问类的任何成员(如构造器、方法、字段),JVM会加载这个类。
-
主类:当运行一个Java程序时,包含
main方法的类会被加载和初始化。
静态字段,成员方法,块的内存管理
用static修饰的东西都会被放进方法区,而不是堆或者栈。
静态字段(静态成员变量):==存在方法区中,而不是堆栈。==由于静态字段属于类的一部分,它们的生命周期与类的生命周期相同。因此,只有当类不再被使用,并且没有任何引用指向该类的ClassLoader时,JVM的垃圾回收器才会考虑卸载类,并回收其静态字段所占用的内存。所以静态字段不要太大,因为他们不容易被回收,容易额外占内存。
静态成员方法:==存在方法区中,而不是堆栈。==另外,当静态方法被调用时,它的执行上下文(包括局部变量、操作数栈等)会被创建在调用它的线程的栈上。每个线程都有自己的栈,用于存储局部变量和调用栈帧(Stack Frames)。
静态块:它的字节码和它所初始化的静态变量都存储在方法区。它所执行的操作是对静态变量的初始化,这些静态变量存储在方法区中。静态初始化块执行完毕后,其代码不再存在于调用栈中。
另外,不要在静态块里再重新创建static变量会报错。静态块是用来初始化的。
static关键字
从上面的资料就可以看出静态字段,成员方法不依附于任何对象,它是属于类的。同时它被所有对象所共享。比方说我有一个class A,根据A我创建了很多对象 a 1 , a 2 . . . . a n a_1,a_2....a_n a1,a2....an。 A , a 1 , a 2 . . . . a n A,a_1,a_2....a_n A,a1,a2....an都共享同一份静态字段。而非静态字段每一个对象都有自己独立的一份。要访问静态字段,静态方法,既可以通过类名A直接访问,也可以通过对象访问。
使用static关键字注意点
由于static字段不依赖于任何对象,因此
- 静态方法不能调用非静态成员,编译会报错。因为加载静态方法时,对象没有创建。此时非静态的成员变量不存在。
- 静态方法不能调用非静态方法。原因同上,编译器无法确定非静态方法中是否含有非静态成员。
static与this
在静态字段或者静态初始化块中,不能使用this关键字,因为this引用的是当前类的实例,而静态字段
但是非静态字段可以使用this访问静态的成员。因为静态字段是共享的
