前言
上篇文章介绍了BaseAnalyzer,AbstractAssignAnalyzer,AssignAnalyzer.其中有很多点分享的不够透彻,这里就对于AssignAnalyzer,什么是静态变量和静态初始块来介绍.
案例
静态变量和静态初始块的案例
本案例的代码如下:
public class AssignDemo {
static int a = 7;
static final int b ;
static {
a = 10;
b = 8;
}
int c = 9;
final int d;
{
c = 12;
d = 12;
}
public void test() {
int c = 10;
}
}
在AssignAnalyzer中,首先调用了analyzeTree方法,这个是在com.sun.tools.javac.comp.Flow.analyzeTree(Env<AttrContext>, TreeMaker)中调用的.如下:
public void analyzeTree(Env<AttrContext> env, TreeMaker make) {
new AliveAnalyzer().analyzeTree(env, make);
new AssignAnalyzer(log, syms, lint, names).analyzeTree(env);
new FlowAnalyzer().analyzeTree(env, make);
new CaptureAnalyzer().analyzeTree(env, make);
}
接着在com.sun.tools.javac.comp.Flow.AbstractAssignAnalyzer.analyzeTree(Env<?>, JCTree) 中对字段进行了初始化,然后调用scan方法开始进行遍历,这部分的代码我们在上一篇文章有介绍过.
最终来到了com.sun.tools.javac.comp.Flow.AbstractAssignAnalyzer.visitClassDef(JCClassDecl)方法,一开始是保存现场的代码,如下:
// 如果tree 没有对应的符号,则直接return
if (tree.sym == null) {
return;
}
JCClassDecl classDefPrev = classDef;
int firstadrPrev = firstadr;
int nextadrPrev = nextadr;
ListBuffer<P> pendingExitsPrev = pendingExits;
接下来,是初始化pendingExits, firstadr, classDef如下:
pendingExits = new ListBuffer<P>();
if (tree.name != names.empty) {// 如果该类不是匿名类
firstadr = nextadr;
}
classDef = tree;
对于当前,由于AssignDemo所对应的树的名字不为空,因此, firstadr = nextadr = 0
接下来时处理static fields.
for (List<JCTree> l = tree.defs; l.nonEmpty(); l = l.tail) {
if (l.head.hasTag(VARDEF)) {
JCVariableDecl def = (JCVariableDecl)l.head;
if ((def.mods.flags & STATIC) != 0) {
VarSymbol sym = def.sym;
if (trackable(sym)) { // 如果该变量是可追踪的(在当前,意味着该变量是final且未赋值的),就保存到vardecls中
newVar(def);
}
}
}
}
这里先说明一下, AssignDemo语法树的defs中有如下成员:
其中,init方法是javac添加的.那么其中,是静态变量的有如下:
- static int a = 7
- static final int b
那么是否这两个字段都在这里处理呢? 问题的关键是在trackable中,如下:
protected boolean trackable(VarSymbol sym) {
return
sym.pos >= startPos &&
((sym.owner.kind == MTH ||
((sym.flags() & (FINAL | HASINIT | PARAMETER)) == FINAL &&
classDef.sym.isEnclosedBy((ClassSymbol)sym.owner))));
}
这里的判断条件是给定符号的位置>= startPos && (sym 属于方法 || (当前的符号是 final && 当前符号是classDef的成员))
对于static int a = 7 来说:
-
sym.pos >= startPos 满足.(a 所对应的pos 为 41,远远大于startPos(2))
-
sym.flags() & (FINAL | HASINIT | PARAMETER)) == FINAL 不满足,这里因为其没有final修饰,同时其初始赋值为7,所以sym.flags() & (FINAL | HASINIT | PARAMETER)) 的结果就是 HASINIT
-
因此,static int a = 7 不会在这里处理,static int a = 7 不是这里所说的static fields.
对于static final int b 来说:
-
sym.pos >= startPos 满足.(a 所对应的pos 为 66,远远大于startPos(2))
-
sym.flags() & (FINAL | HASINIT | PARAMETER)) == FINAL 满足,这里因为其有final修饰,同时没有初始赋值,所以sym.flags() & (FINAL | HASINIT | PARAMETER)) 的结果就是 FINAL
-
同时,该符号是被AssignDemo所对应的符号所包围(Enclosed)的.这是很明显的
-
因此,static final int b 会在这里处理,static final int b 是这里所说的static fields.那么接下来就会调用AbstractAssignAnalyzer.newVar(JCVariableDecl)方法.如下:
void newVar(JCVariableDecl varDecl) {
VarSymbol sym = varDecl.sym;
vardecls = ArrayUtils.ensureCapacity(vardecls, nextadr);// 对vardecls 进行扩容
if ((sym.flags() & FINAL) == 0) {// 如果当前符号不是final的
sym.flags_field |= EFFECTIVELY_FINAL; // 那么就修改该变量的标识符为有效final
}
sym.adr = nextadr;
vardecls[nextadr] = varDecl;
exclVarFromInits(varDecl, nextadr); // 从初始化对应的bitmap中去除
uninits.incl(nextadr);// 加入到未初始化的bitmap中
nextadr++;
}
处理逻辑如下:
- 首先获得b所对应的符号VarSymbol
- 对 vardecls 进行扩容,对于当前是不需要的,vardecls的长度是32,而nextadr是0, vardecls的长度完全满足nextadr.
- 如果VarSymbol不是final的,则设置其修饰符为有效final的,对于当前, VarSymbol是final修饰的,因此这步是不会执行的.
- 设置VarSymbol的adr为nextadr,也就是 0
- 保存VarSymbol到vardecls中,下标是0
- 从初始化对应的bitmap中去除. 这样也就意味着下标为0的变量还未初始化.此时的位图为:00000000000000000000000000000000
- 加入到未初始化的bitmap中.此时的位图为:
10000000000000000000000000000000 - nextadr 自增
static fields 处理完之后,就是处理static initializers.如下:
for (List<JCTree> l = tree.defs; l.nonEmpty(); l = l.tail) {
if (!l.head.hasTag(METHODDEF) &&
(TreeInfo.flags(l.head) & STATIC) != 0) {
scan(l.head);
}
}
满足条件的有:
- static int a = 7
- static final int b
- 静态代码块
以上3者都会调用Flow.BaseAnalyzer.scan(JCTree)方法,区别在于:
-
对于static int a = 7 来说,是变量声明,因此会调用AbstractAssignAnalyzer.visitVarDef(JCVariableDecl),如下:
public void visitVarDef(JCVariableDecl tree) { boolean track = trackable(tree.sym); if (track && tree.sym.owner.kind == MTH) {// 如果当前变量是方法中的 newVar(tree); } if (tree.init != null) { scanExpr(tree.init); if (track) { letInit(tree.pos(), tree.sym); } } }
- 首先,对于a来说,是不可追踪的,这点前面已经有介绍
- 由于是不可追踪的同时该符号的拥有者的类型是TYP,不是MTH,因此不会执行Flow.AbstractAssignAnalyzer.newVar(JCVariableDecl)
- 同时,由于该a有初始化部分–> 7 ,因此,会调用scanExpr方法.而由于7是一个int类型的字面量,那么最终调用的是TreeScanner.visitLiteral(JCLiteral),该方法是空实现.
- 还是因为该符号是不可追踪的,因此是不会调用Flow.AbstractAssignAnalyzer.letInit(DiagnosticPosition, VarSymbol)
-
对于 static final int b来说,同时是变量声明,因此也会调用AbstractAssignAnalyzer.visitVarDef(JCVariableDecl).与static int a = 7 的区别是:
- 该符号是可追踪的,这点在前面有说明
- 其没有初始化部分
因此对于static final int b来说,就相当于是没有处理.
-
对于静态代码块,我们在下篇文章中进行解释.
接下来就是处理实例字段,实例初始化器,这部分的代码和静态处理是一样的,这里就不展开了
总结
- 对于AssignAnalyzer来说,有static 修饰,没有初始化值的字段是static 字段, 没有static 修饰,没有初始化值的字段是实例字段
- 对于AssignAnalyzer来说,有static 修饰,有初始化值的字段是static 初始化器, 没有static 修饰,有初始化值的字段是实例初始化器