最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java?final關(guān)鍵字修飾類、方法、變量的不同作用及實戰(zhàn)指南

 更新時間:2026年01月08日 10:47:36   作者:普通網(wǎng)友  
在Java中final關(guān)鍵字是一個修飾符,它用于表示某個類、方法或變量是不可變的或不可繼承的,這篇文章主要介紹了Java?final關(guān)鍵字修飾類、方法、變量的不同作用及實戰(zhàn)指南,需要的朋友可以參考下

一、引言

你真的懂 final 嗎?我見過不止兩個項目因為誤用 final 栽了大跟頭 —— 前年電商大促,核心庫存類沒加 final 被新人繼承重寫,庫存扣減邏輯被篡改,直接造成超賣損失幾十萬;還有次分布式系統(tǒng)中,接口返回的 final 集合被研發(fā)強行修改內(nèi)容,引發(fā)上下游數(shù)據(jù)不一致。很多人以為 final 就只是 “不能改”,卻忽略了它在線程安全、JVM 優(yōu)化、代碼健壯性上的關(guān)鍵作用。你大概率也踩過 “final 變量能改內(nèi)容”“final 方法能變相重寫” 的坑,讀完這篇,我會把踩過的血淚教訓和生產(chǎn)級用法全告訴你,讓你徹底用對 final。

二、核心知識點解析

1. 從 final 變量開始:為什么新手總搞混 “不可變”?

我?guī)н^的實習生里,80% 都犯過這個錯:寫了final List<String> list = new ArrayList<>();,然后往里面 add 元素,發(fā)現(xiàn)居然能加,就吐槽 “final 沒用”。這是最典型的誤解 —— 新手把 “引用不可變” 和 “對象不可變” 混為一談,甚至有人覺得 final 就是 “常量” 的代名詞。

說白了,final 變量的核心是 **“賦值后不能改指向”**:修飾基本類型(int/long)時,是值不能改;修飾引用類型(對象 / 集合)時,是指向的內(nèi)存地址不能改,但地址里的對象內(nèi)容該咋改還咋改。就像你有把 final 的快遞柜鑰匙,鑰匙本身不能換,但你能往柜子里放、拿東西。

final 修飾類型不可變的對象可變的對象
基本類型(int)變量的值
引用類型(List)變量的內(nèi)存地址集合里的元素
// Java 17+
public class FinalVariableDemo {
    public static void main(String[] args) {
        // ? 新手錯誤認知:final集合不能加元素
        final List<String> goodsList = new ArrayList<>();
        goodsList.add("手機"); // 能執(zhí)行!因為只是改集合內(nèi)容,沒改地址
        System.out.println(goodsList); // 輸出:[手機]

        // ? 真正的不可變:地址和內(nèi)容都不能改
        final List<String> immutableList = List.of("電腦", "平板");
        // immutableList.add("耳機"); // 運行時報錯:UnsupportedOperationException
    }
}

2. final 方法:重寫的坑比你想的深

我曾經(jīng)在支付項目里見過這個 bug:核心的簽名驗證方法verifySign()沒加 final,被子類重寫后,驗證邏輯被改得稀碎,導致支付回調(diào)驗簽失敗。很多人覺得 “方法加 final 只是不能重寫”,卻不知道 JVM 對 final 方法有優(yōu)化 —— 因為不會被重寫,JIT 編譯時能直接內(nèi)聯(lián),性能提升 10% 左右(我在壓測中實測過)。

?? 關(guān)鍵提醒:子類可以定義和 final 方法同名的方法,但這不是 “重寫”(術(shù)語:方法隱藏),只是新方法,多態(tài)場景下會出邏輯混亂。就像你爸把家門鎖死(final),你沒法撬鎖,但能在隔壁自己裝個新鎖,看似一樣,實則不是一個門。

// Java 17+
public class FinalMethodDemo {
    public static void main(String[] args) {
        Parent parent = new Child();
        parent.pay(); // 輸出:父類支付邏輯(不是重寫,是隱藏)
    }
}

class Parent {
    final void pay() {
        System.out.println("父類支付邏輯"); // final方法不能被重寫
    }
}

class Child extends Parent {
    // 不是重寫,只是子類新方法
    void pay() {
        System.out.println("子類支付邏輯");
    }
}

3. final 類:什么時候該把類封死?

我見過有人把所有工具類都加 final,也見過有人給核心業(yè)務(wù)類漏加 final—— 前者導致擴展難,后者被惡意繼承篡改邏輯。final 類的核心作用是 “封死繼承”,適合兩類場景:一是工具類(如 java.lang.Math),不需要擴展且要保證邏輯穩(wěn)定;二是核心業(yè)務(wù)類(如訂單類),防止繼承篡改核心邏輯。

?? 小技巧:Java 17 + 中,final 類和 sealed 類(密封類)是互補的 ——final 是 “完全不讓繼承”,sealed 是 “只讓指定類繼承”,根據(jù)場景選就行。

// Java 17+
final class OrderService { // 訂單核心類,封死繼承
    public void createOrder() {
        System.out.println("創(chuàng)建訂單(核心邏輯,禁止篡改)");
    }
}

// class OrderServiceExt extends OrderService {} // 編譯報錯:無法繼承final類

三、實戰(zhàn)代碼

示例 1:基礎(chǔ)用法(Java 17+)

// Java 17+
public class FinalBasicDemo {
    // final靜態(tài)常量(全大寫,命名規(guī)范)
    public static final double TAX_RATE = 0.06;
    // final實例變量(必須初始化)
    private final String orderId;

    // 構(gòu)造器初始化final實例變量
    public FinalBasicDemo(String orderId) {
        this.orderId = orderId; // 只能賦值一次
    }

    // final方法(不能被重寫)
    final String getOrderId() {
        return this.orderId;
    }

    public static void main(String[] args) {
        FinalBasicDemo demo = new FinalBasicDemo("ORD2025001");
        System.out.println(demo.getOrderId()); // 輸出:ORD2025001
        // demo.orderId = "ORD2025002"; // 編譯報錯:final變量不能重新賦值
    }
}

執(zhí)行結(jié)果:ORD2025001關(guān)鍵說明:final 實例變量必須在聲明時或構(gòu)造器中初始化,賦值后不能改,這是新手最容易忘的點。

示例 2:進階場景 - final 在并發(fā)中的應(yīng)用(Java 17+)

// Java 17+
public class FinalConcurrentDemo {
    // final變量保證多線程下可見性(JVM保證初始化完成后才發(fā)布)
    private final String config;

    public FinalConcurrentDemo() {
        this.config = loadConfig(); // 初始化配置
    }

    private String loadConfig() {
        return "db.url=localhost:3306";
    }

    // 多線程讀取final變量,無線程安全問題
    public String getConfig() {
        return this.config;
    }

    public static void main(String[] args) {
        FinalConcurrentDemo demo = new FinalConcurrentDemo();
        // 10個線程讀取配置
        for (int i = 0; i < 10; i++) {
            new Thread(() -> System.out.println(demo.getConfig())).start();
        }
    }
}

優(yōu)勢說明:final 變量在 JVM 中會被特殊處理,初始化完成后對所有線程可見,無需加 volatile 也能保證可見性,這是并發(fā)編程中常用的優(yōu)化手段。

示例 3:踩坑示范 - final 引用類型修改內(nèi)容(Java 17+)

// Java 17+
public class FinalPitfallDemo {
    public static void main(String[] args) {
        // ? 坑點:以為final集合不能改內(nèi)容
        final HashMap<String, Integer> stockMap = new HashMap<>();
        stockMap.put("手機", 100); // 能執(zhí)行,只是地址不能改
        stockMap.put("手機", 99); // 甚至能修改已有元素的值
        System.out.println(stockMap.get("手機")); // 輸出:99

        // ? 正確做法:用不可變集合
        final Map<String, Integer> immutableStockMap = Map.of("手機", 100);
        // immutableStockMap.put("手機", 99); // 運行時報錯,徹底不可變
    }
}

后果說明:生產(chǎn)環(huán)境中,這種坑會導致 “以為數(shù)據(jù)沒改,實際被偷偷修改”,比如庫存數(shù)據(jù)被篡改,引發(fā)超賣。

示例 4:最佳實踐 - 生產(chǎn)級 final 用法(Java 17+)

// Java 17+
import java.util.Collections;
import java.util.List;

// final核心業(yè)務(wù)類,防止繼承篡改
public final class ProductService {
    // final常量(編譯期確定,JVM優(yōu)化)
    public static final int MAX_STOCK = 1000;
    // final引用 + 不可變集合,雙重保障
    private final List<String> hotProducts;

    // 構(gòu)造器初始化,返回不可變視圖
    public ProductService(List<String> hotProducts) {
        // 防御性拷貝 + 不可變視圖,徹底防止修改
        this.hotProducts = Collections.unmodifiableList(new ArrayList<>(hotProducts));
    }

    // final方法,保證邏輯穩(wěn)定 + JVM內(nèi)聯(lián)優(yōu)化
    public final List<String> getHotProducts() {
        return this.hotProducts; // 返回的集合不能修改
    }

    public static void main(String[] args) {
        List<String> tempList = List.of("手機", "電腦");
        ProductService service = new ProductService(tempList);
        // service.getHotProducts().add("平板"); // 運行時報錯,徹底安全
    }
}

優(yōu)勢說明:結(jié)合 final 引用和不可變集合,既保證地址不變,又保證內(nèi)容不變;final 類 + final 方法,徹底杜絕繼承篡改,適合核心業(yè)務(wù)場景。

四、易錯點與避坑指南

? 常見錯誤 1:混淆 final 引用和對象不可變

  • 錯誤代碼:
    final List<Integer> stockList = new ArrayList<>();
    stockList.add(100); // 以為會報錯,實際能執(zhí)行
    System.out.println(stockList.size()); // 輸出:1
    
  • 實際場景:我在庫存系統(tǒng)中見過這個問題,研發(fā)以為加了 final 就安全,結(jié)果庫存被偷偷修改,導致超賣。
  • 根本原因:final 只鎖 “引用地址”,不鎖 “對象內(nèi)容”,新手把 “引用不可變” 和 “對象不可變” 混為一談。
  • ? 正確做法:
    final List<Integer> stockList = Collections.unmodifiableList(new ArrayList<>());
    // stockList.add(100); // 運行時報錯,徹底不可變
    
  • 防守方案:核心數(shù)據(jù)集合,一律用Collections.unmodifiableXXX或 Java 9 + 的List.of()創(chuàng)建不可變集合。

? 常見錯誤 2:final 方法以為能 “變相重寫”

  • 錯誤代碼:
    class PayService {
        final void pay() { System.out.println("正常支付"); }
    }
    class HackPayService extends PayService {
        void pay() { System.out.println("篡改支付邏輯"); } // 以為是重寫
    }
    
  • 實際場景:我在支付項目中見過,研發(fā)以為子類的同名方法能覆蓋父類 final 方法,上線后發(fā)現(xiàn)支付邏輯混亂,排查了 3 小時才定位到。
  • 根本原因:final 方法不能被重寫,子類同名方法只是 “方法隱藏”,多態(tài)場景下會執(zhí)行父類方法,導致邏輯不符合預(yù)期。
  • ? 正確做法:
    class PayService {
        final void pay() { System.out.println("正常支付"); }
        // 如需擴展,提供鉤子方法
        protected void beforePay() {}
    }
    class HackPayService extends PayService {
        @Override
        protected void beforePay() {
            System.out.println("擴展支付前置邏輯"); // 用鉤子方法擴展,而非重寫
        }
    }
    
  • 防守方案:final 方法如需擴展,提供 protected 的鉤子方法,而非讓子類重寫核心方法。

? 常見錯誤 3:final 變量未初始化導致編譯錯誤

  • 錯誤代碼:
    public class FinalInitDemo {
        private final String userId; // 未初始化
        public FinalInitDemo() {
            // 沒給userId賦值
        }
    }
    
  • 實際場景:新人寫代碼時經(jīng)常忘初始化 final 實例變量,編譯報錯后還不知道原因。
  • 根本原因:final 實例變量必須在 “聲明時”“構(gòu)造器中” 或 “初始化塊中” 賦值,且只能賦值一次,JVM 不允許 final 變量處于未初始化狀態(tài)。
  • ? 正確做法:
    public class FinalInitDemo {
        private final String userId;
        // 構(gòu)造器中初始化
        public FinalInitDemo(String userId) {
            this.userId = userId;
        }
        // 或聲明時初始化
        // private final String userId = "default";
    }
    
  • 防守方案:開發(fā)工具(IDEA)開啟 “final 變量未初始化” 的提示,編碼時優(yōu)先在構(gòu)造器中初始化。

? 常見錯誤 4:過度使用 final 類導致擴展難

  • 錯誤代碼:
    final class CommonUtils { // 普通工具類加final,后續(xù)無法擴展
        public static String formatStr(String str) {
            return str.trim();
        }
    }
    
  • 實際場景:我在中臺項目中見過,前期給所有工具類加 final,后期需要擴展時只能復(fù)制粘貼代碼,導致代碼冗余。
  • 根本原因:把 “核心類需要 final” 的原則濫用在所有類上,忽略了工具類的擴展需求。
  • ? 正確做法:
    // 普通工具類不加final,提供靜態(tài)方法即可
    class CommonUtils {
        // 私有構(gòu)造器,防止實例化
        private CommonUtils() {}
        public static String formatStr(String str) {
            return str.trim();
        }
    }
    // 如需擴展,直接新增方法,無需繼承
    
  • 防守方案:僅對 “核心業(yè)務(wù)類”“不可變工具類(如 Math)” 加 final,普通工具類用 “私有構(gòu)造器 + 靜態(tài)方法” 即可。

? 常見錯誤 5:多線程下 final 變量的發(fā)布問題

  • 錯誤代碼:
    public class FinalPublishDemo {
        private final List<String> data;
        // 錯誤:final變量在構(gòu)造器中逸出
        public FinalPublishDemo() {
            data = new ArrayList<>();
            new Thread(() -> System.out.println(data)).start(); // 構(gòu)造器中發(fā)布
        }
    }
    
  • 實際場景:我在分布式系統(tǒng)中見過,final 變量在構(gòu)造器中發(fā)布,導致其他線程讀取到未初始化完成的對象。
  • 根本原因:JVM 保證 final 變量初始化完成后才對其他線程可見,但構(gòu)造器中發(fā)布對象會打破這個保證,線程可能讀取到未完全初始化的 final 變量。
  • ? 正確做法:
    public class FinalPublishDemo {
        private final List<String> data;
        public FinalPublishDemo() {
            data = new ArrayList<>();
        }
        // 構(gòu)造器完成后再發(fā)布
        public void publish() {
            new Thread(() -> System.out.println(data)).start();
        }
    }
    
  • 防守方案:禁止在構(gòu)造器中發(fā)布 final 變量,必須等構(gòu)造器執(zhí)行完成后再讓其他線程訪問。

五、總結(jié)與延伸

快速回顧

  1. final 變量鎖 “指向”:基本類型值不變,引用類型地址不變,內(nèi)容可變;
  2. final 方法防重寫:JVM 會內(nèi)聯(lián)優(yōu)化,子類同名方法只是隱藏而非重寫;
  3. final 類封繼承:僅用于核心業(yè)務(wù)類 / 不可變工具類,避免過度使用。

延伸學習

  1. 學習 Java 21 的 sealed 類,和 final 類搭配實現(xiàn)靈活的繼承控制;
  2. 研究 JVM 對 final 的優(yōu)化機制(如常量折疊、方法內(nèi)聯(lián));
  3. 了解 final 和 immutable 對象的設(shè)計模式。

面試高頻提問 + 簡潔答案

  1. final 修飾變量的作用?→ 基本類型值不變,引用類型地址不變,保證可見性和不可變;
  2. final 方法能被重寫嗎?→ 不能,子類同名方法是方法隱藏,非重寫;
  3. final 類的使用場景?→ 核心業(yè)務(wù)類(防篡改)、不可變工具類(如 Math);
  4. final 和 volatile 的區(qū)別?→ final 保證不可變 / 可見性,volatile 保證可見性 / 禁止指令重排;
  5. Java 17 中 final 和 sealed 類的區(qū)別?→ final 完全禁止繼承,sealed 只允許指定類繼承。

到此這篇關(guān)于Java final關(guān)鍵字修飾類、方法、變量的不同作用及實戰(zhàn)指南的文章就介紹到這了,更多相關(guān)Java final關(guān)鍵字修飾類、方法、變量內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • java中ArrayList與LinkedList對比詳情

    java中ArrayList與LinkedList對比詳情

    這篇文章主要通過實例對Java中ArrayList與LinkedList進行了對比,需要的朋友可以參考下
    2017-04-04
  • IDEA對使用了第三方依賴jar包的非Maven項目打jar包的問題(圖文詳解)

    IDEA對使用了第三方依賴jar包的非Maven項目打jar包的問題(圖文詳解)

    這篇文章主要介紹了IDEA對使用了第三方依賴jar包的非Maven項目打jar包的問題,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-07-07
  • java封裝前端查詢條件通用版

    java封裝前端查詢條件通用版

    這篇文章主要為大家詳細介紹了java封裝前端查詢條件的通用版,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-05-05
  • Java設(shè)計模式之淺談模板方法模式

    Java設(shè)計模式之淺談模板方法模式

    今天給大家?guī)淼氖顷P(guān)于Java設(shè)計模式的相關(guān)知識,文章圍繞著Java模板方法展開,文中有非常詳細的介紹及代碼示例,需要的朋友可以參考下
    2021-06-06
  • 面試官:java ThreadLocal真的會造成內(nèi)存泄露嗎

    面試官:java ThreadLocal真的會造成內(nèi)存泄露嗎

    ThreadLocal,java面試過程中的“釘子戶”,在網(wǎng)上也充斥著各種有關(guān)ThreadLocal內(nèi)存泄露的問題,本文換個角度,先思考ThreadLocal體系中的ThreadLocalMap為什么要設(shè)計成弱引用
    2021-08-08
  • Java鍵值對Pair的使用方式和操作實現(xiàn)

    Java鍵值對Pair的使用方式和操作實現(xiàn)

    鍵值對是一種常見的數(shù)據(jù)結(jié)構(gòu),它由一個唯一的鍵和與之關(guān)聯(lián)的值組成,本文就來介紹一下Java鍵值對Pair的使用方式和操作實現(xiàn),具有一定的參考價值,感興趣的可以了解一下
    2023-12-12
  • SpringMvc獲取頁面中的參數(shù)方法詳解

    SpringMvc獲取頁面中的參數(shù)方法詳解

    這篇文章主要介紹了SpringMvc獲取頁面中的參數(shù)方法詳解,獲取頁面的參數(shù)通常都是讓類實現(xiàn)設(shè)置HttpServletRequest request接口然后重寫接口中的方法的辦法來得到參數(shù),但是在Springmvc中有其他的獲取方法,需要的朋友可以參考下
    2023-10-10
  • 解決gateway配合nacos路由報錯:Unable to find instance for XXX問題

    解決gateway配合nacos路由報錯:Unable to find instance&

    這篇文章主要介紹了解決gateway配合nacos路由報錯:Unable to find instance for XXX問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2025-06-06
  • Springboot打包部署項目的過程詳解

    Springboot打包部署項目的過程詳解

    這篇文章主要介紹了Springboot打包部署項目的過程,通過jar包方式打包和war方式打包,本文結(jié)合示例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2023-12-12
  • SpringBoot項目解決跨域的四種方案分享

    SpringBoot項目解決跨域的四種方案分享

    在用SpringBoot開發(fā)后端服務(wù)時,我們一般是提供接口給前端使用,但前端通過瀏覽器調(diào)我們接口時,瀏覽器會有個同源策略的限制,即協(xié)議,域名,端口任一不一樣時都會導致跨域,這篇文章主要介紹跨域的幾種常用解決方案,希望對大家有所幫助
    2023-05-05

最新評論

诏安县| 左云县| 秭归县| 定兴县| 石首市| 灵石县| 阿勒泰市| 宝清县| 新民市| 疏勒县| 邵东县| 高平市| 安义县| 绥芬河市| 太和县| 临沧市| 汨罗市| 从江县| 宁蒗| 如皋市| 永川市| 镇赉县| 织金县| 洪洞县| 郎溪县| 呼图壁县| 武威市| 城市| 三明市| 莱阳市| 广水市| 建宁县| 汽车| 普兰县| 平武县| 普安县| 叶城县| 灵宝市| 临西县| 古浪县| 绥宁县|