static 和 final 關鍵字 對實例變數賦初始值的影響

来源:https://www.cnblogs.com/hapjin/archive/2018/07/21/9348404.html
-Advertisement-
Play Games

static 和 final 關鍵字 對實例變數賦初始值的影響 最近一直在看《深入理解Java虛擬機》,在看完了對象記憶體分配、Class文件格式之後,想深扒一下實例變數是如何被賦上初始值的這個問題的細節。 在2.3.1小節中講對象創建的時候,講到記憶體分配有兩種方式:一種是指針碰撞;另一種是空閑列表。 ...


static 和 final 關鍵字 對實例變數賦初始值的影響

最近一直在看《深入理解Java虛擬機》,在看完了對象記憶體分配、Class文件格式之後,想深扒一下實例變數是如何被賦上初始值的這個問題的細節。

在2.3.1小節中講對象創建的時候,講到記憶體分配有兩種方式:一種是指針碰撞;另一種是空閑列表。

而選擇哪種分配方式是由JAVA堆是否規整決定,而JAVA堆是否規整則由虛擬機所採用的垃圾收集器是否帶壓縮整理功能決定。

我們不管記憶體分配採用何種方式,當記憶體分配完成後,虛擬機將分配到的記憶體空間都初始化為零值,這裡的零值代表各個類型的初始值。比如 int類型的實例變數的初始值為0,boolean類型的預設初始值為false。因此,這裡的初始值,是從虛擬視角看到的初始值。但是從JAVA程式視角來看,我們給變數賦上的值,是在 <init> 方法中進行的。下麵討論從JAVA程式視角看,實例變數如何被賦上初始值的。

如果一個實例變數在被final修飾後,該變數賦初始值的時候,要麼在聲明實例變數的時候賦值,如下所示:

public class VariableTest {
    private final int a;//編譯期報錯:Variable a may not be initialized
}

public class VariableTest {
    private final int a = 127;//在聲明實例變數的時候賦值
}

或者在構造方法中為變數賦初始值:

public class VariableTest {
    private final int a ;
    public VariableTest() {
    a = 127;
    }
}

這與 final 關鍵字的“不可變性”有關,至於加了final修飾後,在編譯或者運行的時候,對這個變數的影響是什麼,我就不知道怎麼解釋了。

接下來,再來看,在實例變數上使用 static 修飾,該實例變數就變成了類變數。

在第6章中講到:

對於非 static 類型的變數的賦值是在實例構造器<init>方法中進行的;----這與我們前面的描述相符。

對於類變數有兩種方式賦初始值:

  • 在類構造器<clinit>方法中賦值
  • 使用ConstantValue屬性賦值

而對於類變數的這兩種賦值方式,選用哪一種是則編譯器來決定的:對於Sun javac編譯器,當實例變數使用static 和 final 修飾的時候,就會生成 ConstantValue屬性為之賦值。

但是,這裡需要註意的是:只有基本類型(int、long...)和String類型才能生成 ConstantValue屬性。如下所示:

public class VariableTest {
    private static final Integer b = 1;
    private static final int a = 1;
    private static final String s = "test";
}

(我猜想基本類型的包裝類型,比如int類型的包裝類Integer,應該也是採用ConstantValue屬性方式來賦初始值的吧)

那這裡就有個問題:為什麼只有基本類型(int、long..)和String類型才能生成 ConstantValue屬性呢?

下麵來說下ConstantValue屬性是個啥?

從頭說起:class文件結構中有一個常量池,常量池存放 字面量 和 符號引用。字面量就是下麵的一些“字元串”

public class VariableTest {
    private static final String s = "test";
}

那"test" 就是一個字面量。

那符號引用是啥呢?有這三類:

  • 類和介面的全限定名稱
  • 欄位的名稱和描述符
  • 方法的名稱和描述符

那描述符又是什麼呢?

描述符 的作用是 用來描述欄位的數據類型、方法的參數列表(包括數量、類型、參數順序)和返回值

描述符 就是按某種規則 來描述欄位或者方法。欄位就是我們寫代碼時定義的某個實例變數。

舉例來說:

public class VariableTest {
    public void inc() {
    }
}

上面那個inc方法的描述符就是:()V,為什麼是這樣呢?那就是書上177頁解釋嘛。

感覺有點跑偏了。

class文件格式裡面,有一種存儲結構叫做 方法表,方法表裡面有一個 attribute_info 類型的 屬性(暫且叫屬性吧,參考書上表6-11)。

這個attribute_info 類型的 屬性 是一個屬性集合,裡面又定義了若幹屬性(參考表6-13),其中就有一個名為ConstantValue的屬性,而ConstantValue屬性的 值是一個常量池的索引號,而根據:表6-3常量池的項目類型來看,只有一些:CONSTANT_Utf8_info、CONSTANT_Integer_info、CONSTANT_Long_info…類型的字面量。(CONSTANT_Utf8_info 對應String類型 字面量)

因此,:只有基本類型(int、long..)和String類型才能生成 ConstantValue屬性了。

既然常量池是存儲字面量和符號引用的,而ConstantValue屬性值 就指向了常量池中的索引號,這就是一個被static final 修飾的實例變數的 初始值 的來源了(或者說:一個被static final修飾的 基本類型/String類型的 變數的初始值,其實是存儲在Class文件的常量池中的)。

介紹到這裡,那如果一個實例變數沒有使用final修飾,而只使用了static修飾,那它就應該在類構造器clinit方法中賦初始值了。

最後總結一下:

根據實例變數的類型來分:一類是 基本類型和String類型;另一類是 引用類型(比如自定義的類)

如果一個實例變數被static修飾,那它就是一個類變數。

對於 基本類型和String類型的實例變數:

  • 該實例變數被 static 修飾了,則在 clinit方法中賦初始值(類變數嘛)
  • 該實例變數被 final 修飾了,在 init方法中賦初始值(實例變數嘛)
  • 該實例變數同時被 static 和 final 修飾了,通過ConstantValue屬性方式賦初始值(有相應的字面量類型支持嘛)

對於 其他引用類型的實例變數:

  • 該實例變數被 static 修飾了,則在 clinit方法中賦初始值(類變數嘛)
  • 該實例變數被 final 修飾了,在 init方法中賦初始值(實例變數嘛)
  • 該實例變數同時被 static 和 final 修飾了,在 clinit方法中賦初始值

說了這麼多,final關鍵字如何影響 類變數的初始化呢?

在7.2節中說的:類載入的時機,給出了一張類的生命周期的示意圖:

上面有一個準備階段,還有一個初始化的階段。

如果一個類變數沒有被final修飾,如下:

public class VariableTest {
    private static int a = 123;
}

類變數a 在準備階段,被賦值為0,在初始化階段被賦值為123

如果該類變數被final修飾了,如下:

public class VariableTest {
    private static final int a = 123;
}

類變數a 在準備階段就被賦值為123。這是因為 a 是一個基本類型的變數,被static 和 final 修飾後,能夠生成ConstantValue屬性。

那麼我想對於下麵這個 mylist 這個變數:在準備階段應該被賦值為null,在初始化階段被賦值為 指向一個ArrayList對象吧。

public class VariableTest {
    private static final List<String> mylist = new ArrayList<>();
}

參考:《深入理解Java虛擬機》


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • groupby()函數同時返回分組關鍵字和一個與關鍵字相對應的可迭代對象,也可以使用defaultdict()創建一個一對多字典方便進行隨機訪問 ...
  • 先安裝VS2017,然後在安裝WDK,WDK會自動關聯到VS2017中,不用你任何操作,自動在新建項目中可以找到驅動開發。 如果以上安裝完成後,在VS2017中新建項目中沒有發現WDK,那麼需要進行修複。 修複的方法:進入WDK安裝後的文件夾中,找到Vsix這個文件夾,雙擊運行WDK.vsix,程式 ...
  • 1 // C++函數和類 13-函數與string對象.cpp: 定義控制台應用程式的入口點。 2 // 3 4 #include "stdafx.h" 5 #include 6 #include 7 #include 8 #include 9 #include 10 using namespace... ...
  • 1 // C++函數和類 05-返回類型.cpp: 定義控制台應用程式的入口點。 2 // 3 4 #include "stdafx.h" 5 #include 6 #include 7 #include 8 #include 9 #include 10 using namespace std; 1... ...
  • 1 // C++函數和類 01-函數.cpp: 定義控制台應用程式的入口點。 2 // 3 4 #include "stdafx.h" 5 #include 6 #include 7 #include 8 #include 9 #include 10 using namespace std; 11 ... ...
  • c/c++ 用前序和中序,或者中序和後序,創建二叉樹 用前序和中序創建二叉樹 用後序和中序創建二叉樹 bintreemain.c "完整代碼" 編譯方法:g++ g nodestack.c nodequeue.c bintree.c bintreemain.c ...
  • static 用法 1.static 變數 static變數又稱為靜態變數,靜態變數保存在方法區靜態域中,一個類的靜態變數被其所有實例共用。 2.static方法 靜態方法不與包含它的任何對象關聯,即使沒有創建對象,也可使用,例: 1 public class StaticTest { 2 3 pu ...
  • ng-app="angular_app" 範圍 ng-controller="angular_controller" 控制器 ng-init="findAll()" 初始化方法 雙向數據綁定 就是angular的ng-model屬性同時具備了表單元素中的name與value兩個屬性,既可以提交表單數 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...