本篇內容主要講解“final關鍵字的小細節有哪些”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“final關鍵字的小細節有哪些”吧!
正文
final關鍵字是一個常用的關鍵字,可以修飾變量、方法、類,用來表示它修飾的類、方法和變量不可改變,下面就聊一下使用 final 關鍵字的一些小細節。
細節一、final 修飾類成員變量和實例成員變量的賦值時機
對于類變量:
鴻蒙官方戰略合作共建——HarmonyOS技術社區
聲明變量的時候直接賦初始值
在靜態代碼塊中給類變量賦初始值
如下代碼所示:
public class FinalTest { //a變量直接賦值 private final static int a = 1; private final static int b; //b變量通過靜態代碼塊賦值 static { b=2; } }
對于實例變量:
鴻蒙官方戰略合作共建——HarmonyOS技術社區
在聲明變量的時候直接賦值
在非靜態代碼塊中賦值
在構造器中賦初始化值
如下代碼所示:
public class FinalTest { //c變量在在聲明時直接賦值 private final int c =1; private final int d; private final int e; //d變量在非靜態代碼塊中賦值 { d=2; } //e變量在構造器中賦值 FinalTest(){ e=3; } }
細節二、當 final 修飾的成員變量未對它進行初始化時,會出現錯誤嗎?
答:會出現錯誤。因為 java 語法規定,final 修飾的成員變量必須由程序員顯示的初始化,系統不會對變量進行隱式的初始化。
如下圖所示,未初始化變量就會出現編譯錯誤:
細節三、final 修飾基本類型變量和引用類型變量的區別
如果 fianl 修飾的是一個基本數據類型的數據,一旦賦值后就不能再次更改。
那么 final 修飾的是引用數據類型呢?這個引用的變量能夠改變嗎?
看下面的代碼:
public class FinalTest { //在聲明final實例成員變量時進行賦值 private final static Student student = new Student(50, "Java"); public static void main(String[] args) { //對final引用數據類型student進行更改 student.age = 100; System.out.println(student.toString()); } static class Student { private int age; private String name; public Student(int age, String name) { this.age = age; this.name = name; } @Override public String toString() { return "Student{" + "age=" + age + ", name='" + name + '\'' + '}'; } } } //下面是打印結果 Student{age=100, name='Java'}
從打印結果可以看到:引用數據類型變量 student 的 age 屬性修改成 100,是可以修改成功的。
結論:
鴻蒙官方戰略合作共建——HarmonyOS技術社區
當 final 修飾基本數據類型變量時,不能對基本數據類型變量重新賦值,因此基本數據類型變量不能被改變。
對于引用類型變量而言,它僅僅保存的是一個引用,final 只保證這個引用類型變量所引用的地址不會發生改變,即一直引用這個對象,但這個對象里面的屬性是可以改變的。
細節四、final 修飾局部變量的場景
fianl 局部變量由程序員進行顯示的初始化,如果 final 局部變量進行初始化之后就不能再次進行更改。
如果 final 變量未進行初始化,可以進行賦值,并且只能進行一次賦值,一旦賦值之后再次賦值就會出錯。
下面的代碼演示 final 修飾局部變量的情況:
細節五、final 修飾方法會對重載有影響嗎?重寫呢?
對于重載:final 修飾方法后是可以重載的
如下代碼:
public class FinalTest { public final void test(){ } //重載方法不會出現問題 public final void test(String test){ } }
對于重寫:當父類的方法被 final 修飾的時候,子類不能重寫父類的該方法
如上代碼所示,可以看到會出現 cannot override ,overridden method is final 的編譯錯誤提示
細節六、final 修飾類的場景
當用final修飾一個類時,表明這個類不能被繼承。也就是說,如果一個類你永遠不會讓他被繼承,就可以用 final 進行修飾。
final 類中的成員變量可以根據需要設為 final,但是要注意 final 類中的所有成員方法都會被隱式地指定為 final 方法。
細節七、寫 final 域的重排序規則,你知道嗎?
這個規則是指禁止對 final 域的寫重排序到構造函數之外,這個規則的實現主要包含了兩個方面:
鴻蒙官方戰略合作共建——HarmonyOS技術社區
JMM 禁止編譯器把 final 域的寫重排序 到 構造函數 之外
編譯器會在 final 域寫之后,構造函數 return 之前,插入一個 StoreStore 屏障。這個屏障可以禁止處理器把 final 域的寫重排序到構造函數之外
給舉個例子,要不太抽象了,先看一段代碼
public class FinalTest{ private int a; //普通域 private final int b; //final域 private static FinalTest finalTest; public FinalTest() { a = 1; // 1. 寫普通域 b = 2; // 2. 寫final域 } public static void writer() { finalTest = new FinalTest(); } public static void reader() { FinalTest demo = finalTest; // 3.讀對象引用 int a = demo.a; //4.讀普通域 int b = demo.b; //5.讀final域 } }
假設線程 A 在執行 writer()方法,線程 B 執行 reader()方法。
由于變量 a 和變量 b 之間沒有依賴性,所以就有可能會出現下圖所示的重排序
由于普通變量 a 可能會被重排序到構造函數之外,所以線程 B 就有可能讀到的是普通變量 a 初始化之前的值(零值),這樣就可能出現錯誤。
而 final 域變量 b,根據重排序規則,會禁止 final 修飾的變量 b 重排序到構造函數之外,從而 b 能夠正確賦值,線程 B 就能夠讀到 final 域變量 b初始化后的值。
結論:寫 final 域的重排序規則可以確保在對象引用為任意線程可見之前,對象的 final 域已經被正確初始化過了,而普通域就不具有這個保障。
細節八:讀 final 域的重排序規則,你知道嗎?
這個規則是指在一個線程中,初次讀對象引用和初次讀該對象包含的 final 域,JMM 會禁止這兩個操作的重排序。
還是上面那段代碼
public class FinalTest{ private int a; //普通域 private final int b; //final域 private static FinalTest finalTest; public FinalTest() { a = 1; // 1. 寫普通域 b = 2; // 2. 寫final域 } public static void writer() { finalTest = new FinalTest(); } public static void reader() { FinalTest demo = finalTest; // 3.讀對象引用 int a = demo.a; //4.讀普通域 int b = demo.b; //5.讀final域 } }
假設線程 A 在執行 writer()方法,線程 B 執行 reader()方法。
線程 B 可能就會出現下圖所示的重排序
可以看到,由于讀對象的普通域被重排序到了讀對象引用的前面,就會出現線程 B 還未讀到對象引用就在讀取該對象的普通域變量,這顯然是錯誤的操作。而 final 域的讀操作就“限定”了在讀 final 域變量前已經讀到了該對象的引用,從而就可以避免這種情況。
結論:讀 final 域的重排序規則可以確保在讀一個對象的 final 域之前,一定會先讀包含這個 final 域的對象的引用。
到此,相信大家對“final關鍵字的小細節有哪些”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。