OUZHANBO

对于我这种菜鸡来说,毕业等于失业

0%

static关键字声明的变量的加载问题

static 关键字声明的变量的加载机制

前段时间在学习单例模式的时候遇到一种写法事用静态内部类来实现的,发现自己不是很懂静态内部类的加载时序,后来衍生到了 static 关键字修饰的变量方法都不是很懂,在网上找了一些文章看了下,大概明白他们的加载的机制和时序,下面来说下。之前我知道类的静态变量会在类被加载的时候初始化,但是类什么时候会被加载呢,这个我之前都没有考虑过。以下是我觉得我平时用得比较多的类加载方式:

​ 1.调用类的静态变量或者静态方法

​ 2.new 一个类的对象

​ 3.Class.forName 动态加载类

下面用代码测试一下第一种情况,在 Main 这个运行类型中的 main 入口方法中调用 Example 类的静态方法 staticField4,这样就会加载 Example 类并且先初始化这个类中的静态变量,静态代码块和静态代码的运行顺序取决于代码的编写顺序,然后再执行 main 方法,测试代码如下:

StaticField:

1
2
3
4
5
6
7
public class StaticField {

public StaticField(String string)
{
System.out.println(string);
}
}

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Example {

static {
staticField3 = new StaticField("静态代码块中静态变量staticField3被初始化");
}
static StaticField staticField1 = new StaticField("静态变量staticField1被初始化");
static StaticField staticField2 = new StaticField("静态变量staticField2被初始化");
static final StaticField staticField3;

static void staticMethod1(){
System.out.println("静态方法staticMethod1被执行");
}
}

Main:

1
2
3
4
5
6
public class Main {

public static void main(String[] args) {
Example.staticMethod1();
}
}

结果如下:

1
2
3
4
静态代码块中静态变量staticField3被初始化
静态变量staticField1被初始化
静态变量staticField2被初始化
静态方法staticMethod1被执行

从结果可以看出这个是先执行静态变量的初始化然后才执行静态方法,静态变量的初始化执行顺序取决代码的顺序并且静态代码块中需要初始化的静态变量可以写在静态代码块之后并且该静态变量可以声明为 final,但是不可以在静态代码块中对 final 声明的静态变量初始化两次,这样编译不会通过,实例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
public class Example extends ExampleParent{

static {
staticField3 = new StaticField("静态代码块中静态变量staticField3被初始化");
//编译会不通过,说staticField3已经被赋值
//staticField3 = new StaticField("静态代码块中静态变量staticField3被初始化");
}
static StaticField staticField1 = new StaticField("静态变量staticField1被初始化");
static StaticField staticField2 = new StaticField("静态变量staticField2被初始化");
static final StaticField staticField3;
}

这里可能还有一个问题,如果在 main 中不调用 Example 的静态方法而是直接调用其中一个静态变量就只会初始化那个被调用的静态变量呢,下面修改 Main 的 main 方法来测试一下:

1
2
3
4
5
6
public class Main {

public static void main(String[] args) {
System.out.println(Example.staticField2);
}
}

结果如下:

1
2
3
4
静态代码块中静态变量staticField3被初始化
静态变量staticField1被初始化
静态变量staticField2被初始化
StaticField@4554617c

可以看到初始化静态变量的动作并没有发生任何的改变,还是和之前一样按顺序的将所有的静态变量初始化了。另外两种情况就在这里简单演示一下,结果都基本一样

第二种情况,new Example

1
2
3
4
5
6
public class Main {

public static void main(String[] args) {
new Example();
}
}

结果如下:

1
2
3
静态代码块中静态变量staticField3被初始化
静态变量staticField1被初始化
静态变量staticField2被初始化

第三种情况,Class.forName 动态加载类

1
2
3
4
5
6
7
8
9
10
public class Main {

public static void main(String[] args) {
try {
Class.forName("Example");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}

结果如下:

1
2
3
静态代码块中静态变量staticField3被初始化
静态变量staticField1被初始化
静态变量staticField2被初始化

有一种很迷惑我的情况是不可以的,代码如下

1
2
3
4
5
6
public class Main {

public static void main(String[] args) {
System.out.println(Example.class);
}
}

结果如下:

1
class Example

这种情况并没有加载 Example 类,这个很迷惑人,需要注意。

父子类 static 关键字修饰的变量的加载顺序

如果出现集成的情况,那么会先执行父类的静态代内容(静态变量和静态代码块),然后再执行子类的静态内容(静态变量和静态代码块)

认证代码如下:

StaticField:

1
2
3
4
5
6
7
public class StaticField {

public StaticField(String string)
{
System.out.println(string);
}
}

ParentExample:

1
2
3
4
5
6
7
8
public class ExampleParent {

static StaticField parentStaticField1 = new StaticField("父类的静态变量parentStaticField1被初始化");
static StaticField parentStaticField2;
static {
parentStaticField2 = new StaticField("父类的静态代码块中静态变量parentStaticField2被初始化");
}
}

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Example extends ExampleParent {

static {
staticField3 = new StaticField("静态代码块中静态变量staticField3被初始化");
}
static StaticField staticField1 = new StaticField("静态变量staticField1被初始化");
static StaticField staticField2 = new StaticField("静态变量staticField2被初始化");
static StaticField staticField3;

static void staticMethod1(){
System.out.println("静态方法staticMethod1被执行");
}
}

Main:

1
2
3
4
5
6
public  class Main {

public static void main(String[] args) {
System.out.println(Example.staticField1);
}
}

结果如下:

1
2
3
4
5
6
父类的静态变量parentStaticField1被初始化
父类的静态代码块中静态变量parentStaticField2被初始化
静态代码块中静态变量staticField3被初始化
静态变量staticField1被初始化
静态变量staticField2被初始化
StaticField@4554617c

因为在加载 Example 这个子类之前先加载了 ParentExample 这个父类,所以感觉通过子类可以获取父类的静态成员(静态变量和静态方法)。

认证代码如下:

给父类 ParentExaple 加个静态方法:

1
2
3
4
5
6
7
8
9
10
11
12
public class ExampleParent {

static StaticField parentStaticField1 = new StaticField("父类的静态变量parentStaticField1被初始化");
static StaticField parentStaticField2;
static {
parentStaticField2 = new StaticField("父类的静态代码块中静态变量parentStaticField2被初始化");
}

static void parentStaticMethod1(){
System.out.println("父类的静态方法parentStaticMethod1被执行");
}
}

调用父类的静态变量:

1
2
3
4
5
6
public class Main {

public static void main(String[] args) {
System.out.println(Example.parentStaticField1);
}
}

结果如下:

1
2
3
父类的静态变量parentStaticField1被初始化
父类的静态代码块中静态变量parentStaticField2被初始化
StaticField@4554617c

调用父类的静态方法:

1
2
3
4
5
6
public  class Main {

public static void main(String[] args) {
Example.parentStaticField3();
}
}

结果如下:

1
2
3
父类的静态变量parentStaticField1被初始化
父类的静态代码块中静态变量parentStaticField2被初始化
父类的静态方法parentStaticMethod1被执行

从结果发现子类可以调用父类的静态成员(静态变量或者静态方法),但是如果只是通过子类调用父类的静态成员(静态变量或者静态方法)并不会加载子类,指挥加载父类。

static 修饰的内部类的加载机制(静态内部类)

先看看下面的测试代码:

StaticField:

1
2
3
4
5
6
public class StaticField {

public StaticField(String string){
System.out.println(string);
}
}

Outer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Outer {

static StaticField outerStaticField1 = new StaticField("外部类静态变量innserStaticField1被初始化");
static {
StaticField outerStaticField2 = new StaticField("外部类静态变量innserStaticField2被初始化");
}

static class Innser{
static StaticField innserStaticField1 = new StaticField("内部类静态变量innserStaticField1被初始化");
static {
StaticField innserStaticField2 = new StaticField("内部类静态变量innserStaticField2被初始化");
}
}
}

如果在 main 方法只是调用外部类

Main:

1
2
3
4
5
6
7
8
public  class Main {

public static void main(String[] args) {
//获取Outer类的静态变量outerStaticField1
System.out.println(Outer.outerStaticField1);

}
}

结果如下:

1
2
3
外部类静态变量innserStaticField1被初始化
外部类静态变量innserStaticField2被初始化
StaticField@4554617c

如果只在 main 方法中调用内部类

1
2
3
4
5
6
7
public  class Main {

public static void main(String[] args) {
//获取静态内部类Innser的静态变量innserStaticField1
System.out.println(Outer.Innser.innserStaticField1);
}
}

结果如下:

1
2
3
内部类静态变量innserStaticField1被初始化
内部类静态变量innserStaticField2被初始化
StaticField@4554617c

可以看出只调用外部类只会初始加载外部类并不会加载静态内部类,只调用静态内部类只记载静态内部类并不会记载外部类。