先看个例子:
public class Demo{
static Temp t1 = new Temp();
final static Temp t2 = new Temp();
static String s1 = new String("heihei");
final static String s2 = "haha";
static int i1 = 1;
final static int i2 = 2;
}
编译器处理可以理解下面的情形:
public class Demo{
//类初始化调用,只会调用一次
void static constructor <clinit>(){
//初始化t1
new-instance v0,Temp
invoke-direct v0,Temp;<init>()V
sput-object v0,t1
//初始化t2
new-instance v0,Temp
invoke-direct v0,Temp;<init>()V
sput-object v0,t2
//初始化s1
new-instance v0,Ljava/lang/String
const-string v1,"heihei"
invoke-direct {v0,v1} ,Ljava/lang/String;-><init>(Ljava/lang/String)V
sput-object v0,Demo;->s1:Ljava/lang/String
//初始化i1
const/4 v0,0x1
sput v0,Demo;->i1:I
}
}
可以看到final static String s2 = "haha";final static int i2 = 2;本没有在<clinit>中初始化,看起来好像和上一篇的域编译矛盾。
实际上在执行dvmInitClass执行<clinit>前还有执行init$Fields方法。init$Fields方法会给static变量赋初值,如果是引用类型默认为NULL。
因此在执行init$Fields方法后:
t1 = NULL;
t2 = NULL;
s1 = NULL;
s2 = "haha";
i1 = 0;
i2 = 2;
其中t1、t2,s1,i1首先赋予了默认值,然后在<clinit>得到初始化。
static 和 final static修饰field区别:
- final static修饰的原始类型和非引用String类型不会被翻译到
<clinit>,而是在类初始化执行init$Fields方法时得到了初始化赋值。 - final static修饰的引用类型,仍然会在
<clinit>初始化。
热部署解决方案
- final static修饰的引用类型由于是在
<clinit>初始化初始化,所以没法走热部署。 - final static修饰的原始类型参数和非引用String类型参数会编译到dex文件中,所以当final static修饰的原始类型被引用到时会被立即替换,final static修饰的非引用String类型会被常量池索引id替换,因此热部署模式下,最终所有引用该final static的域都会被修改,所以支持热部署。

本文探讨了Java中final static字段的编译处理,解释了为何未在类初始化时显式初始化的final static字段仍能得到默认值。在DVM执行dvmInitClass方法前,静态变量会先被赋予默认值,引用类型默认为NULL。对于final static修饰的原始类型和非引用String,其值会在类初始化时通过方法赋值,而final static修饰的引用类型则在初始化时赋值,不支持热部署。相反,final static修饰的原始类型和非引用String会编译到DEX文件,支持热部署时的域替换。
868

被折叠的 条评论
为什么被折叠?



