我的编程空间,编程开发者的网络收藏夹
学习永远不晚

javacfinal变量未赋值检测案例讲解

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

javacfinal变量未赋值检测案例讲解

前言

我们在前面介绍AssignAnalyzer时,对AssignAnalyzer.letInit(DiagnosticPosition, VarSymbol)方法进行了简单的介绍.本文就举一个案例,来深入理解一下.

案例

案例代码如下:

public class CheckInitError {

	static final int b;
	
	public CheckInitError(){
		
		
	}
	
}

本代码在IDE环境(如Eclipse)中报如下错误:The blank final field b may not have been
initialized.
那么它是如何检测出错误的,本文就来揭秘.(Eclipse中内置了Java编译器)

解析

还是在javac中的Flow阶段,最终来到了AbstractAssignAnalyzer.analyzeTree(Env<?>, JCTree)方法,在该方法中,又调用了Flow.BaseAnalyzer.scan(JCTree)方法,进而调用AbstractAssignAnalyzer.visitClassDef(JCClassDecl)方法,同时,由于字段b是可追踪的,因此会在处理静态字段时调用AbstractAssignAnalyzer.newVar(JCVariableDecl)方法,将b所对应的符号保存在vardecls中,下标位置为0(下标为0的原因是它是第一个变量).这部分的内容是在上篇文章中有所介绍的,本文不再展开.

在AbstractAssignAnalyzer#visitClassDef方法中,处理完静态字段,静态初始块,实例字段,实例初始块之后,就会处理方法(包括构造器).

那么由于在CheckInitError中只定义了一个构造器,因此如下代码只会处理一次:

for (List<JCTree> l = tree.defs; l.nonEmpty(); l = l.tail) {
    if (l.head.hasTag(METHODDEF)) {
        scan(l.head);
    }
}

由于CheckInitError构造器所对应的树节点是JCMethodDecl,因此最终会调用AbstractAssignAnalyzer.visitMethodDef(JCMethodDecl).

在AbstractAssignAnalyzer#visitMethodDef中,一开始,是进行判断:

// 如果该方法的语句体为null,则意味着该方法是一个抽象方法,不处理.
if (tree.body == null) {
    return;
}

// 忽略处理合成方法,但是合成的lambda方法还是处理的
if ((tree.sym.flags() & (SYNTHETIC | LAMBDA_METHOD)) == SYNTHETIC) {
    return;
}

然后是保存现场:

final Bits initsPrev = new Bits(inits);
final Bits uninitsPrev = new Bits(uninits);
int nextadrPrev = nextadr;
int firstadrPrev = firstadr;
int returnadrPrev = returnadr;

Assert.check(pendingExits.isEmpty());

接下来,判断当前方法是一个构造函数,其构造函数中的第一个语句不是this(…)的语句吗?

 boolean isInitialConstructor =
                    TreeInfo.isInitialConstructor(tree);

思考: 为何会在这里做这样的判断? AssignAnalyzer的定位是检查final变量是否有多次赋值,那么假设我们在一个类中final 字段(未初始化的),那么不管你有多少个构造函数,那么就应该在一个最终调用的构造器中对这个变量进行初始化.举例:

public class CheckInitError {
	
	final int b;
	public CheckInitError(){
		//this(3); // 第1种
		b = 3; // 第2种
		// 如果第1行,第2行注释掉,则报错,因为b没有最终初始化
	}
	public CheckInitError(int a ){
		b  = a;
	}
	
} 

那么对于CheckInitError来说, CheckInitError方法构造函数中的第一个语句不是this(…),而是super();因此isInitialConstructor为true.为啥呢? javac会在构造器的第一行插入super(),至于是在什么条件下插入,如何插入,我们后续介绍,本文不表.

由于isInitialConstructor等于true,因此,如下代码是不会执行的:

if (!isInitialConstructor) {
    firstadr = nextadr;
}

接下来,是处理方法的参数,那么由于在案例中是没有参数的,因此如下代码是不会执行的:

for (List<JCVariableDecl> l = tree.params; l.nonEmpty(); l = l.tail) {
    JCVariableDecl def = l.head;
    scan(def);
    // 参数应该有PARAMETER的修饰符,否则就是一个错误
    Assert.check((def.sym.flags() & PARAMETER) != 0, "Method parameter 	without PARAMETER flag");
	initParam(def);
}

protected void initParam(JCVariableDecl def) {
	inits.incl(def.sym.adr);
	uninits.excl(def.sym.adr);
}

这段代码的作用是依次处理参数,然后将参数加入到变量已经初始化的位图中,至于为啥? 原因很简单:参数是调用方传递的,当方法执行时,形参是肯定有值的(初始化的),否则就是一个错误

接下来处理方法体,由于javac默认添加了一个super()语句,那么就会进行实质的处理(副作用).但是这部分与本文关联不大,本文就不展开了.

方法体执行完之后,如果isInitialConstructor为true,则判断构造器是否将类中的变量(final变量但是没有初始赋值的)都初始化了.如下:

if (isInitialConstructor) {
    boolean isSynthesized = (tree.sym.flags() &
                             GENERATEDCONSTR) != 0; // 判断该构造器是否是合成的
    // 这里判断的是构造器是否将类中的变量(final变量但是没有初始赋值的)都初始化了.
    for (int i = firstadr; i < nextadr; i++) {
        JCVariableDecl vardecl = vardecls[i];
        VarSymbol var = vardecl.sym;
        if (var.owner == classDef.sym) {
            // choose the diagnostic position based on whether
            // the ctor is default(synthesized) or not
            if (isSynthesized) {
                checkInit(TreeInfo.diagnosticPositionFor(var, vardecl),
                    var, "var.not.initialized.in.default.constructor");
            } else {
                checkInit(TreeInfo.diagEndPos(tree.body), var);
            }
        }
    }
}

那么对于当前,由于该构造器不是合成的,因此isSynthesized为false.同时,在该类中只定义了一个变量–> b,那么因此循环只会执行一次(firstadr = 0,nextadr = 1,这部分的内容在上篇文章有介绍)

在循环中,通过下标取得b对应的VarSymbol,调用AssignAnalyzer.checkInit(DiagnosticPosition, VarSymbol, String)方法进行判断.如下:

void checkInit(DiagnosticPosition pos, VarSymbol sym, String errkey) {
    if ((sym.adr >= firstadr || sym.owner.kind != TYP) &&
        trackable(sym) &&
        !inits.isMember(sym.adr)) {
        log.error(pos, errkey, sym);
        inits.incl(sym.adr);
    }
}

对于当前来说,符号是可跟踪的,但是在inits(初始化变量位图)中不存在对应的下标,因此会调用log#error方法,进行错误日志输出.然后将其加入到inits(这样做的目的,是为了一次编译能获得更多的错误信息)

对于当前,是报如下错误:

1

然后,是pendingExits 处理和恢复现场,这部分的内容,我们后续文章会举例子进行讲解.

总结

通过本文,我们可以知道Eclipse中报如下错误:The blank final field b may not have been
initialized. 是在Flow阶段由AssignAnalyzer检测出来的.

到此这篇关于javac final变量未赋值检测案例讲解的文章就介绍到这了,更多相关javac final变量未赋值内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

javacfinal变量未赋值检测案例讲解

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

javacfinal变量未赋值检测案例讲解

这篇文章主要介绍了javacfinal变量未赋值检测案例讲解,通过本文,我们可以知道Eclipse中报如下错误:Theblankfinalfieldbmaynothavebeeninitialized.是在Flow阶段由AssignAnalyzer检测出来的,需要的朋友可以参考下
2022-12-20

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录