<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關(guān)鍵字的這8個小細節(jié),你get到幾個?

          共 7584字,需瀏覽 16分鐘

           ·

          2021-05-14 06:22

          前言

          今天來聊 final 關(guān)鍵字,因為最近在看的幾本書都講到了 final 關(guān)鍵字,發(fā)現(xiàn)好多小細節(jié)自己都忽視了,抽空總結(jié)了一下,分享給大家。

          正文

          final關(guān)鍵字是一個常用的關(guān)鍵字,可以修飾變量、方法、類,用來表示它修飾的類、方法和變量不可改變,下面就聊一下使用 final 關(guān)鍵字的一些小細節(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. 在構(gòu)造器中賦初始化值

          如下代碼所示:

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

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

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

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

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

          如果 fianl 修飾的是一個基本數(shù)據(jù)類型的數(shù)據(jù),一旦賦值后就不能再次更改。

          那么 final 修飾的是引用數(shù)據(jù)類型呢?這個引用的變量能夠改變嗎?

          看下面的代碼:

          public class FinalTest {
              //在聲明final實例成員變量時進行賦值
              private final static Student student = new Student(50"Java");

              public static void main(String[] args) {
                  //對final引用數(shù)據(jù)類型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 + '\'' +
                              '}';
                  }
              }
          }

          //下面是打印結(jié)果
          Student{age=100, name='Java'}

          從打印結(jié)果可以看到:引用數(shù)據(jù)類型變量 student 的 age 屬性修改成 100,是可以修改成功的。

          結(jié)論:

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

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

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

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

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

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

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

          如下代碼:

          public class FinalTest {
              public final void test(){

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

              }
          }

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

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

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

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

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

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

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

          1. JMM 禁止編譯器把 final 域的寫重排序 到 構(gòu)造函數(shù) 之外
          2. 編譯器會在 final 域?qū)懼螅瑯?gòu)造函數(shù) return 之前,插入一個 StoreStore 屏障。這個屏障可以禁止處理器把 final 域的寫重排序到構(gòu)造函數(shù)之外

          給舉個例子,要不太抽象了,先看一段代碼

          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域
              }
          }

          假設(shè)線程 A 在執(zhí)行 writer()方法,線程 B 執(zhí)行 reader()方法。

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

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

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

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

          細節(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域
              }
          }

          假設(shè)線程 A 在執(zhí)行 writer()方法,線程 B 執(zhí)行 reader()方法。

          線程 B 可能就會出現(xiàn)下圖所示的重排序

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

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

          結(jié)束

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

          瀏覽 35
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  五月丁香影音先锋 | 欧美三级高清在线观看 | 开心久久婷婷综合 | 国产亚洲欧美精品久久久久久 | 欧美伊人在线 |