首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

【Java 每日一題】20170113:final 字段初始化陷阱與構造器語義分析

     編輯:Java 綜合教學

每日一題深度解析:final 實例字段未在構造器初始化導致編譯錯誤

題目代碼

package Jan2017;
public class Ques0113 {
    private final String id; // final 實例字段
    
    public Ques0113() {
        System.out.println("構造函數");
    }
    
    public void setId(String id) {
        id = id; // 參數遮蔽字段,賦值給參數自身而非字段
    }
}

問題:主程序能否正常編譯通過?

答案:不能編譯通過

詳細原因分析

1. final 實例字段初始化規則 (JLS §4.12.4, §8.3.1.2)

  • final 實例字段**必須**在以下位置之一**確定賦值** (definitely assigned):
    1. 聲明處直接初始化:private final String id = "default";
    2. 實例初始化塊:{ id = "init"; }
    3. 每一個構造器中 (或構造器調用的其他構造器 this(...) 中)
  • 本題 id 無聲明賦值、無初始化塊、構造器 Ques0113() 未賦值 → **編譯錯誤**:variable id might not have been initialized。

2. setId 方法的參數遮蔽 Bug (邏輯錯誤,非編譯錯誤)

public void setId(String id) { // 參數 id 遮蔽字段 this.id
    id = id; // 將參數賦值給參數自身,無任何效果
    // 正確寫法:this.id = id; (但 final 字段不可在構造器外修改,所以此方法對 final 字段無效)
}

即使編譯通過,setId 也無法修改 final 字段,且參數遮蔽導致邏輯空轉。

修正版本 (可編譯運行)

public class Ques0113Fixed {
    private final String id;
    
    // 方案一:構造器初始化 (推薦,不可變對象模式)
    public Ques0113Fixed(String id) {
        this.id = id; // 明確賦值給字段
        System.out.println("構造函數: " + id);
    }
    
    // 方案二:聲明處初始化
    // private final String id = "default";
    
    // final 字段不可提供 setter,若需修改請去掉 final
    public String getId() { return id; }
    
    public static void main(String[] args) {
        new Ques0113Fixed("123"); // 正常編譯運行
    }
}

知識點總結

概念規則本題體現
final 實例字段構造器結束前必須確定賦值一次構造器未賦值 → 編譯失敗
final 靜態字段靜態初始化塊/聲明處賦值,類加載階段完成—
參數遮蔽方法參數/局部變量同名字段時遮蔽,需用 this. 區分setId 中 id = id 無效
不可變對象final 字段 + 無 setter + 構造器注入 = 線程安全共享修正版體現

延伸思考

  1. 若將 id 改為非 final,代碼能編譯通過嗎?能,但 setId 仍有遮蔽 Bug。
  2. 構造器中調用 setId("x") 能否滿足 final 初始化要求?不能,編譯器不做方法內部流分析,仍報未初始化。
  3. final 引用類型字段指向的對象內部狀態可變嗎?可變,final 只保證引用不變,不保證對象不可變。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved