initialized. 是在Flow階段由AssignAnalyzer檢測(cè)出來的,需要的朋友可以參考下" />

欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

javac final變量未賦值檢測(cè)案例講解

 更新時(shí)間:2022年12月20日 10:49:37   作者:一個(gè)努力的碼農(nóng)  
這篇文章主要介紹了javac final變量未賦值檢測(cè)案例講解,通過本文,我們可以知道Eclipse中報(bào)如下錯(cuò)誤:The blank final field b may not have been
initialized. 是在Flow階段由AssignAnalyzer檢測(cè)出來的,需要的朋友可以參考下

前言

我們?cè)谇懊娼榻BAssignAnalyzer時(shí),對(duì)AssignAnalyzer.letInit(DiagnosticPosition, VarSymbol)方法進(jìn)行了簡單的介紹.本文就舉一個(gè)案例,來深入理解一下.

案例

案例代碼如下:

public class CheckInitError {

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

本代碼在IDE環(huán)境(如Eclipse)中報(bào)如下錯(cuò)誤:The blank final field b may not have been
initialized.
那么它是如何檢測(cè)出錯(cuò)誤的,本文就來揭秘.(Eclipse中內(nèi)置了Java編譯器)

解析

還是在javac中的Flow階段,最終來到了AbstractAssignAnalyzer.analyzeTree(Env<?>, JCTree)方法,在該方法中,又調(diào)用了Flow.BaseAnalyzer.scan(JCTree)方法,進(jìn)而調(diào)用AbstractAssignAnalyzer.visitClassDef(JCClassDecl)方法,同時(shí),由于字段b是可追蹤的,因此會(huì)在處理靜態(tài)字段時(shí)調(diào)用AbstractAssignAnalyzer.newVar(JCVariableDecl)方法,將b所對(duì)應(yīng)的符號(hào)保存在vardecls中,下標(biāo)位置為0(下標(biāo)為0的原因是它是第一個(gè)變量).這部分的內(nèi)容是在上篇文章中有所介紹的,本文不再展開.

在AbstractAssignAnalyzer#visitClassDef方法中,處理完靜態(tài)字段,靜態(tài)初始?jí)K,實(shí)例字段,實(shí)例初始?jí)K之后,就會(huì)處理方法(包括構(gòu)造器).

那么由于在CheckInitError中只定義了一個(gè)構(gòu)造器,因此如下代碼只會(huì)處理一次:

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

由于CheckInitError構(gòu)造器所對(duì)應(yīng)的樹節(jié)點(diǎn)是JCMethodDecl,因此最終會(huì)調(diào)用AbstractAssignAnalyzer.visitMethodDef(JCMethodDecl).

在AbstractAssignAnalyzer#visitMethodDef中,一開始,是進(jìn)行判斷:

// 如果該方法的語句體為null,則意味著該方法是一個(gè)抽象方法,不處理.
if (tree.body == null) {
    return;
}

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

然后是保存現(xiàn)場(chǎng):

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());

接下來,判斷當(dāng)前方法是一個(gè)構(gòu)造函數(shù),其構(gòu)造函數(shù)中的第一個(gè)語句不是this(…)的語句嗎?

 boolean isInitialConstructor =
                    TreeInfo.isInitialConstructor(tree);

思考: 為何會(huì)在這里做這樣的判斷? AssignAnalyzer的定位是檢查final變量是否有多次賦值,那么假設(shè)我們?cè)谝粋€(gè)類中final 字段(未初始化的),那么不管你有多少個(gè)構(gòu)造函數(shù),那么就應(yīng)該在一個(gè)最終調(diào)用的構(gòu)造器中對(duì)這個(gè)變量進(jìn)行初始化.舉例:

public class CheckInitError {
	
	final int b;
	public CheckInitError(){
		//this(3); // 第1種
		b = 3; // 第2種
		// 如果第1行,第2行注釋掉,則報(bào)錯(cuò),因?yàn)閎沒有最終初始化
	}
	public CheckInitError(int a ){
		b  = a;
	}
	
} 

那么對(duì)于CheckInitError來說, CheckInitError方法構(gòu)造函數(shù)中的第一個(gè)語句不是this(…),而是super();因此isInitialConstructor為true.為啥呢? javac會(huì)在構(gòu)造器的第一行插入super(),至于是在什么條件下插入,如何插入,我們后續(xù)介紹,本文不表.

由于isInitialConstructor等于true,因此,如下代碼是不會(huì)執(zhí)行的:

if (!isInitialConstructor) {
    firstadr = nextadr;
}

接下來,是處理方法的參數(shù),那么由于在案例中是沒有參數(shù)的,因此如下代碼是不會(huì)執(zhí)行的:

for (List<JCVariableDecl> l = tree.params; l.nonEmpty(); l = l.tail) {
    JCVariableDecl def = l.head;
    scan(def);
    // 參數(shù)應(yīng)該有PARAMETER的修飾符,否則就是一個(gè)錯(cuò)誤
    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);
}

這段代碼的作用是依次處理參數(shù),然后將參數(shù)加入到變量已經(jīng)初始化的位圖中,至于為啥? 原因很簡單:參數(shù)是調(diào)用方傳遞的,當(dāng)方法執(zhí)行時(shí),形參是肯定有值的(初始化的),否則就是一個(gè)錯(cuò)誤

接下來處理方法體,由于javac默認(rèn)添加了一個(gè)super()語句,那么就會(huì)進(jìn)行實(shí)質(zhì)的處理(副作用).但是這部分與本文關(guān)聯(lián)不大,本文就不展開了.

方法體執(zhí)行完之后,如果isInitialConstructor為true,則判斷構(gòu)造器是否將類中的變量(final變量但是沒有初始賦值的)都初始化了.如下:

if (isInitialConstructor) {
    boolean isSynthesized = (tree.sym.flags() &
                             GENERATEDCONSTR) != 0; // 判斷該構(gòu)造器是否是合成的
    // 這里判斷的是構(gòu)造器是否將類中的變量(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);
            }
        }
    }
}

那么對(duì)于當(dāng)前,由于該構(gòu)造器不是合成的,因此isSynthesized為false.同時(shí),在該類中只定義了一個(gè)變量–> b,那么因此循環(huán)只會(huì)執(zhí)行一次(firstadr = 0,nextadr = 1,這部分的內(nèi)容在上篇文章有介紹)

在循環(huán)中,通過下標(biāo)取得b對(duì)應(yīng)的VarSymbol,調(diào)用AssignAnalyzer.checkInit(DiagnosticPosition, VarSymbol, String)方法進(jìn)行判斷.如下:

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);
    }
}

對(duì)于當(dāng)前來說,符號(hào)是可跟蹤的,但是在inits(初始化變量位圖)中不存在對(duì)應(yīng)的下標(biāo),因此會(huì)調(diào)用log#error方法,進(jìn)行錯(cuò)誤日志輸出.然后將其加入到inits(這樣做的目的,是為了一次編譯能獲得更多的錯(cuò)誤信息)

對(duì)于當(dāng)前,是報(bào)如下錯(cuò)誤:

1

然后,是pendingExits 處理和恢復(fù)現(xiàn)場(chǎng),這部分的內(nèi)容,我們后續(xù)文章會(huì)舉例子進(jìn)行講解.

總結(jié)

通過本文,我們可以知道Eclipse中報(bào)如下錯(cuò)誤:The blank final field b may not have been
initialized. 是在Flow階段由AssignAnalyzer檢測(cè)出來的.

到此這篇關(guān)于javac final變量未賦值檢測(cè)案例講解的文章就介紹到這了,更多相關(guān)javac final變量未賦值內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Netty分布式客戶端接入流程初始化源碼分析

    Netty分布式客戶端接入流程初始化源碼分析

    這篇文章主要介紹了Netty分布式客戶端接入流程初始化源碼分析,有關(guān)channelConfig有關(guān)的初始化過程剖析,有需要的朋友可以借鑒參考下,希望能夠有所幫助
    2022-03-03
  • Java防鎖屏小程序代碼實(shí)例

    Java防鎖屏小程序代碼實(shí)例

    這篇文章主要介紹了Java防鎖屏小程序代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-09-09
  • SpringBoot實(shí)現(xiàn)多數(shù)據(jù)源的切換實(shí)踐

    SpringBoot實(shí)現(xiàn)多數(shù)據(jù)源的切換實(shí)踐

    這篇主要介紹了SpringBoot實(shí)現(xiàn)多數(shù)據(jù)源的切換,本文基于AOP來實(shí)現(xiàn)數(shù)據(jù)源的切換,文中通過示例代碼介紹的非常詳細(xì),感興趣的小伙伴們可以參考一下
    2022-03-03
  • 靈活控制任務(wù)執(zhí)行時(shí)間的Cron表達(dá)式范例

    靈活控制任務(wù)執(zhí)行時(shí)間的Cron表達(dá)式范例

    這篇文章主要為大家介紹了靈活控制任務(wù)執(zhí)行時(shí)間的Cron表達(dá)式范例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-10-10
  • SpringBoot集成RocketMQ的使用示例

    SpringBoot集成RocketMQ的使用示例

    RocketMQ是阿里巴巴開源的一款消息中間件,性能優(yōu)秀,功能齊全,被廣泛應(yīng)用在各種業(yè)務(wù)場(chǎng)景,本文就來介紹一下SpringBoot集成RocketMQ的使用示例,感興趣的可以了解一下
    2023-11-11
  • Java中和隊(duì)列相關(guān)的基本操作

    Java中和隊(duì)列相關(guān)的基本操作

    在Java中,隊(duì)列是一種常用的數(shù)據(jù)結(jié)構(gòu),用于存儲(chǔ)和管理元素。Java提供了Queue接口和其實(shí)現(xiàn)類,包括LinkedList和ArrayDeque等。隊(duì)列的基本操作包括入隊(duì)(enqueue)、出隊(duì)(dequeue)、獲取隊(duì)首元素(peek)和判斷隊(duì)列是否為空(isEmpty)。
    2023-09-09
  • Spring+Vue整合UEditor富文本實(shí)現(xiàn)圖片附件上傳的方法

    Spring+Vue整合UEditor富文本實(shí)現(xiàn)圖片附件上傳的方法

    這篇文章主要介紹了Spring+Vue整合UEditor富文本實(shí)現(xiàn)圖片附件上傳的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-07-07
  • 使用idea開發(fā)javaWeb應(yīng)用程序的思路(實(shí)現(xiàn)用戶的增刪改查)

    使用idea開發(fā)javaWeb應(yīng)用程序的思路(實(shí)現(xiàn)用戶的增刪改查)

    這篇文章主要介紹了使用idea開發(fā)javaWeb應(yīng)用程序的思路(實(shí)現(xiàn)用戶的增刪改查),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-01-01
  • Java8中List轉(zhuǎn)Map(Collectors.toMap) 的技巧分享

    Java8中List轉(zhuǎn)Map(Collectors.toMap) 的技巧分享

    在最近的工作開發(fā)之中,慢慢習(xí)慣了很多Java8中的Stream的用法,很方便而且也可以并行的去執(zhí)行這個(gè)流,這篇文章主要給大家介紹了關(guān)于Java8中List轉(zhuǎn)Map(Collectors.toMap) 的相關(guān)資料,需要的朋友可以參考下
    2021-07-07
  • java自定義類加載器代碼示例

    java自定義類加載器代碼示例

    這篇文章主要介紹了java自定義類加載器代碼示例,具有一定借鑒價(jià)值,需要的朋友可以了解下。
    2017-12-12

最新評(píng)論