深入理解 Java 中的 final 關鍵字

来源:https://www.cnblogs.com/wupeixuan/archive/2019/10/28/11750053.html
-Advertisement-
Play Games

final 是Java 中重要關鍵字之一,可以應用於類、方法以及變數上。這篇文章中將講解什麼是 final 關鍵字?將變數、方法和類聲明為 final 代表了什麼?使用 final 的好處是什麼? final 關鍵字是什麼? final 在 Java 中是一個保留的關鍵字,可以聲明成員變數、方法、類 ...


final 是Java 中重要關鍵字之一,可以應用於類、方法以及變數上。這篇文章中將講解什麼是 final 關鍵字?將變數、方法和類聲明為 final 代表了什麼?使用 final 的好處是什麼?

final 關鍵字是什麼?

final 在 Java 中是一個保留的關鍵字,可以聲明成員變數、方法、類以及本地變數。一旦你將引用聲明作 final,你將不能改變這個引用了,編譯器會檢查代碼,如果試圖將變數再次初始化的話,編譯器會報編譯錯誤。

final 變數

凡是對成員變數或者本地變數(在方法中的或者代碼塊中的變數稱為本地變數)聲明為 final 的都叫作 final 變數。final 變數經常和 static 關鍵字一起使用,作為常量。下麵是 final 變數的例子:

public static final String NAME = "wupx";
NAME = new String("wupx"); //invalid compilation error

final 變數是只讀的。

final 方法

final 也可以聲明方法,Java 里用 final 修飾符去修飾一個方法的唯一正確用途就是表達:這個方法原本是一個虛方法,現在通過 final 來聲明這個方法不允許在派生類中進一步被覆寫(override)。

Java 中非私有的成員方法預設都是虛方法,而虛方法就可以在派生類中被覆寫。為了保證某個類上的某個虛方法不在派生類中被進一步覆寫,就需要使用 final 修飾符來聲明,讓編譯器(例如 javac)與 JVM 共同檢查並保證這個限制總是成立。

下麵引用 R 大 在知乎上的回答來打破“用 final 修飾方法可以讓對這個方法的調用變快”的流言:

曾經有一種廣為流傳的說法是用final修飾方法可以讓對這個方法的調用變快。這種說法在現代主流的優化JVM上都是不成立的(例如Oracle JDK / OpenJDK HotSpot VM、IBM J9 VM、Azul Systems Zing VM等)。這是因為:能用final修飾的虛方法,其派生類中必然不可能存在對其覆寫的版本,於是可以判定這個虛方法只有一個可能的調用目標;而如果此時把這個final修飾符去掉,這些先進的JVM都可以通過“類層次分析”(Class Hierarchy Analysis,CHA)來發現這個方法在派生類中沒有進一步覆寫的版本,於是同樣可以判定這個虛方法只有一個可能的調用目標。然後兩者的優化程度會一模一樣,無論是從“不需要通過虛分派而可以直接調用目標”(稱為“去虛化”,devirtualization)還是從“便於內聯”的角度看,這兩種情況都是一樣的。

而如果某個類層次結構中原本某個虛方法就存在多個覆寫版本的話,那麼本來也不可能對這個虛方法加上final修飾,所以就算這種情況下去虛化變得困難,鍋也不能讓“因為沒用final修飾”來背。

使用final關鍵字在現代主流的優化JVM上不會提升性能。

下麵是 final 方法的例子:

class User{
    public final String getName(){
        return "user:wupx";
    }
}

class Reader extends User{
    @Override
    public final String getName(){
        return "reader wupx"; //compilation error: overridden method is final
    }
}

final 類

使用 final 來修飾的類叫作 final 類,final類通常功能是完整的,它們不能被繼承,Java 中有許多類是 final 的,比如 String, Interger 以及其他包裝類。下麵是 final 類的實例:

final class User{

}

class Reader extends User{  //compilation error: cannot inherit from final class

}

記憶體模型中的 final

對於 final 變數,編譯器和處理器都要遵守兩個重排序規則:

  • 構造函數內,對一個 final 變數的寫入,與隨後把這個被構造對象的引用賦值給一個變數,這兩個操作之間不可重排序
  • 首次讀一個包含 final 變數的對象,與隨後首次讀這個 final 變數,這兩個操作之間不可以重排序

實際上這兩個規則也正是針對 final 變數的寫與讀。寫的重排序規則可以保證,在對象引用對任意線程可見之前,對象的 final 變數已經正確初始化了,而普通變數則不具有這個保障;讀的重排序規則可以保證,在讀一個對象的 final 變數之前,一定會先讀這個對象的引用。如果讀取到的引用不為空,根據上面的寫規則,說明對象的 final 變數一定以及初始化完畢,從而可以讀到正確的變數值。

如果 final 變數的類型是引用型,那麼構造函數內,對一個 final 引用的對象的成員域的寫入,與隨後在構造函數外把這個被構造對象的引用賦值給一個引用變數,這兩個操作之間不能重排序。

實際上這也是為了保證 final 變數在對其他線程可見之前,能夠正確的初始化完成。

final 關鍵字的好處

下麵為使用 final 關鍵字的一些好處:

  • final 關鍵字提高了性能,JVM 和 Java 應用都會緩存 final 變數
  • final 變數可以安全的在多線程環境下進行共用,而不需要額外的同步開銷

總結

  • final 關鍵字可以用於成員變數、本地變數、方法以及類
  • final 成員變數必須在聲明的時候初始化或者在構造器中初始化,否則就彙報編譯錯誤
  • 不能夠對 final 變數再次賦值
  • 本地變數必須在聲明時賦值
  • 在匿名類中所有變數都必須是 final 變數
  • final 方法不能被重寫
  • final 類不能被繼承
  • 介面中聲明的所有變數本身是 final 的
  • final 和 abstract 這兩個關鍵字是反相關的,final 類就不可能是 abstract 的
  • 沒有在聲明時初始化 final 變數的稱為空白 final 變數(blank final variable),它們必須在構造器中初始化,或者調用 this() 初始化,不這麼做的話,編譯器會報錯final變數(變數名)需要進行初始化
  • 按照 Java 代碼慣例,final 變數就是常量,而且通常常量名要大寫
  • 對於集合對象聲明為 final 指的是引用不能被更改

參考

《Java編程思想》

https://www.zhihu.com/question/66083114

file


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

-Advertisement-
Play Games
更多相關文章
  • 用 JavaScript 實現網頁鼓樂器,相關的初始代碼在 JavaScript30 官網和 GitHub 上已經存在。我把 sound 文件夾下的音頻全部替換掉了,一些相關解釋也直接在註釋中標明。 下麵是最終成品地址,可能有些卡頓,音頻載入也比較慢: 結果展示 index.html: <!DOCT ...
  • <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv=" ...
  • 前言 在上一篇中,我們已經對組件有了更加進一步的認識,從組件的創建構造器到組件的組成,進而到組件的使用,.從組件的基本使用、組件屬性,以及自定義事件實現父子通訊和巧妙運用插槽slot分發內容,進一步的認識到組件在Vue中的核心地位.。 而今天,我們將對vue中的基本指令進行瞭解彙總,何為指令? 指令 ...
  • <!DOCTYPE html> <html> <head> <title>組件的切換</title> <meta charset="utf-8"> </head> <body> <!-- 方式一:結合v-if及v-else 只能實現兩個組件的切換--> <div id="app"> <!-- 添加事... ...
  • css 常見的css樣式: 列表: 無序列表: list-style-type 設置無序列表前面的列表符號 disc 表示實心圓 square表示實心方塊 circle表示空心圓 none 表示去掉列表符號 list-style-position 設置列表前面的項目符號是否包含在整個標簽寬度範圍之內 ...
  • 結果: ...
  • 摘要 基於react的框架開發一個頂部固定滑動式的酷炫導航欄,當導航欄置頂時,導航欄沉浸在背景圖片里;當滑鼠滑動滾輪時,導航欄固定滑動並展示下拉樣式。 JS部分 相關技術棧:react、antd、react-router。詳細的技術棧應用請參考官方文檔的使用說明。 * 展示主頁App.jsx組件代碼 ...
  • 強制類型轉換: 字元轉數值 parseInt();從左向右一次轉換,能轉則轉,不能轉停止;如果第一位就不能轉,直接NaN;不識別小數點。 parseFloat();等同於parseInt,同時可以識別小數點 Math.round();嚴格轉換,不允許出現任何非數字的字元,否則NaN;取最接近的整數 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...