一个有趣的问题(java静态字段)

问题

在jake wharton的Twitter上看到他提得这样一个问题:
ques
于是测试了一下,发现很有趣,于是记录下来。

测试类

两个测试类如下

1
2
3
class TestMethodA {
static String name = "akioss";
}

1
2
3
class TestMethodB {
static final String name = "akioss";
}

查看字节码

通过javap指令查看两个类的字节码

classA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class com.akioss.TestMethodA {
static java.lang.String name;

com.akioss.TestMethodA();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return

static {};
Code:
0: ldc #2 // String akioss
2: putstatic #3 // Field name:Ljava/lang/String;
5: return
}

可以看到除了默认的构造方法之外,还有一个静态代码块,执行了putstatic指令

classB

1
2
3
4
5
6
7
8
9
class com.akioss.TestMethodB {
static final java.lang.String name;

com.akioss.TestMethodB();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
}

Tips

之所以会产生这样的情况,就是在于final修饰符(好吧,这是废话 - -!),在讲明为何会出现上述情况之前,先来温习一下staticfinal两个的区别吧。

  • static:static修饰成员变量时,并不会随着类的实例化再去分配空间,从上边putstatic指令也可以看出来,但有一点它是变量。
  • final+static:final和static一同使用,那么被其修饰的值便成了全局常量,即不可变了。
    所以大胆猜测final+static修饰的值一开始就会被jvm分配到常量池,不会在类中去作这一步操作。而static修饰的成员变量,实际是在类实例化的时候执行了一段static代码块,将成员变量putstatic,为了验证,改写了一下TestMethodA.java

    TestMethodA

    1
    2
    3
    4
    5
    6
    7
    class TestMethodA {
    static String name = "akioss";

    static{
    name = "jake";
    }
    }

其字节码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class com.akioss.TestMethodA {
static java.lang.String name;

com.akioss.TestMethodA();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return

static {};
Code:
0: ldc #2 // String akioss
2: putstatic #3 // Field name:Ljava/lang/String;
5: ldc #4 // String jake
7: putstatic #3 // Field name:Ljava/lang/String;
10: return
}

可以发现,同一个类中,在static代码块中对静态成员变量name赋值时,与声明name成员变量走的同一代码块。