<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          final的8個小細節(jié),聽說只有高手才知道!你知道幾個?

          共 4225字,需瀏覽 9分鐘

           ·

          2021-01-28 18:52

          final關鍵字是一個常用的關鍵字,可以修飾變量、方法、類,用來表示它修飾的類、方法和變量不可改變,下面就聊一下使用 final 關鍵字的一些小細節(jié)。

          細節(jié)一、final 修飾類成員變量和實例成員變量的賦值時機

          對于類變量:

          1. 聲明變量的時候直接賦初始值
          2. 在靜態(tài)代碼塊中給類變量賦初始值

          如下代碼所示:

          public?class?FinalTest?{??? //a變量直接賦值
          ????private?final?static??int?a?=?1;

          ????private?final?static??int?b;
          ????//b變量通過靜態(tài)代碼塊賦值
          ????static?{
          ????????b=2;
          ????}
          }

          對于實例變量:

          1. 在聲明變量的時候直接賦值
          2. 在非靜態(tài)代碼塊中賦值
          3. 在構造器中賦初始化值

          如下代碼所示:

          public?class?FinalTest?{
          ????//c變量在在聲明時直接賦值
          ????private?final??int?c?=1;
          ????private?final??int?d;
          ????private?final??int?e;
          ????//d變量在非靜態(tài)代碼塊中賦值
          ????{
          ????????d=2;
          ????}
          ????//e變量在構造器中賦值
          ????FinalTest(){
          ????????e=3;
          ????}
          }

          細節(jié)二、當 final 修飾的成員變量未對它進行初始化時,會出現錯誤嗎?

          答:會出現錯誤。因為 java 語法規(guī)定,final 修飾的成員變量必須由程序員顯示的初始化,系統不會對變量進行隱式的初始化。

          如下圖所示,未初始變量就會出現編譯錯誤:

          細節(jié)三、final 修飾基本類型變量和引用類型變量的區(qū)別

          如果 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,是可以修改成功的。

          結論:

          1. 當 final 修飾基本數據類型變量時,不能對基本數據類型變量重新賦值,因此基本數據類型變量不能被改變。
          2. 對于引用類型變量而言,它僅僅保存的是一個引用,final 只保證這個引用類型變量所引用的地址不會發(fā)生改變,即一直引用這個對象,但這個對象里面的屬性是可以改變的。

          細節(jié)四、final 修飾局部變量的場景

          fianl 局部變量由程序員進行顯示的初始化,如果 final 局部變量進行初始化之后就不能再次進行更改。

          如果 final 變量未進行初始化,可以進行賦值,并且只能進行一次賦值,一旦賦值之后再次賦值就會出錯。

          下面的代碼演示 final 修飾局部變量的情況:

          細節(jié)五、final 修飾方法會對重載有影響嗎?重寫呢?

          對于重載:final 修飾方法后是可以重載的

          如下代碼:

          public?class?FinalTest?{
          ????public?final?void?test(){

          ????}
          ????//重載方法不會出現問題
          ????public?final?void?test(String?test){

          ????}
          }

          對于重寫:當父類的方法被 final 修飾的時候,子類不能重寫父類的該方法

          如上代碼所示,可以看到會出現 cannot override ,overridden method is final 的編譯錯誤提示

          細節(jié)六、final 修飾類的場景

          當用final修飾一個類時,表明這個類不能被繼承。也就是說,如果一個類你永遠不會讓他被繼承,就可以用 final 進行修飾。

          final 類中的成員變量可以根據需要設為 final,但是要注意 final 類中的所有成員方法都會被隱式地指定為 final 方法。

          細節(jié)七、寫 final 域的重排序規(guī)則,你知道嗎?

          這個規(guī)則是指禁止對 final 域的寫重排序到構造函數之外,這個規(guī)則的實現主要包含了兩個方面:

          1. JMM 禁止編譯器把 final 域的寫重排序 到 構造函數 之外
          2. 編譯器會在 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 在執(zhí)行 writer()方法,線程 B 執(zhí)行 reader()方法。

          由于變量 a 和變量 b 之間沒有依賴性,所以就有可能會出現下圖所示的重排序

          由于普通變量 a 可能會被重排序到構造函數之外,所以線程 B 就有可能讀到的是普通變量 a 初始化之前的值(零值),這樣就可能出現錯誤。

          而 final 域變量 b,根據重排序規(guī)則,會禁止 final 修飾的變量 b 重排序到構造函數之外,從而 b 能夠正確賦值,線程 B 就能夠讀到 final 域變量 b初始化后的值。

          結論:寫 final 域的重排序規(guī)則可以確保在對象引用為任意線程可見之前,對象的 final 域已經被正確初始化過了,而普通域就不具有這個保障。

          細節(jié)八:讀 final 域的重排序規(guī)則,你知道嗎?

          這個規(guī)則是指在一個線程中,初次讀對象引用和初次讀該對象包含的 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 在執(zhí)行 writer()方法,線程 B 執(zhí)行 reader()方法。

          線程 B 可能就會出現下圖所示的重排序

          可以看到,由于讀對象的普通域被重排序到了讀對象引用的前面,就會出現線程 B 還未讀到對象引用就在讀取該對象的普通域變量,這顯然是錯誤的操作。而 final 域的讀操作就“限定”了在讀 final 域變量前已經讀到了該對象的引用,從而就可以避免這種情況。

          結論:讀 final 域的重排序規(guī)則可以確保在讀一個對象的 final 域之前,一定會先讀包含這個 final 域的對象的引用。

          結束

          今天給大家總結了一下使用 final 關鍵字容易忽視的一些小細節(jié),看完希望你能有所收獲。


          往期推薦

          Java中的Switch都支持String了,為什么不支持long?


          對象復制的7種方法,還是Spring的最好用!


          分布式ID生成的9種方法,特好用!


          關注我,每天陪你進步一點點!

          瀏覽 28
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  啪啪啪免费看 | 中文字幕在线观看第二页 | 婷婷五月开心五月 | 青青操女人 | 能看的三级网站 |