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

          Java 這個高級特性,很多人直呼好用!

          共 10943字,需瀏覽 22分鐘

           ·

          2021-06-03 02:19

          戳這里,加關注哦~

          泛型是 Java 的高級特性之一,如果想寫出優(yōu)雅而高擴展性的代碼,或是想讀得懂一些優(yōu)秀的源碼,泛型是繞不開的檻。本文介紹了什么是泛型、類型擦除的概念及其實現(xiàn),最后總結了泛型使用的最佳實踐。

          前言

          想寫一下關于 Java 一些高級特性的文章,雖然這些特性在平常實現(xiàn)普通業(yè)務時不必使用,但如果想寫出優(yōu)雅而高擴展性的代碼,或是想讀得懂一些優(yōu)秀的源碼,這些特性又是不可避免的。

          如果對這些特性不了解,不熟悉特性的應用場景,使用時又因為語法等原因困難重重,很難讓人克服惰性去使用它們,所以身邊總有一些同事,工作了很多年,卻從沒有用過 Java 的某些高級特性,寫出的代碼總是差那么一點兒感覺。

          為了避免幾年后自己的代碼還是非常 low,我準備從現(xiàn)在開始深入理解一下這些特性。本文先寫一下應用場景最多的泛型。

          泛型是什么

          首先來說泛型是什么。泛型的英文是 generic,中文意思是通用的、一類的,結合其應用場景,我理解泛型是一種 通用類型。但我們一般指泛型都是指其實現(xiàn)方式,也就是 將類型參數(shù)化

          對于 Java 這種強類型語言來說,如果沒有泛型的話,處理相同邏輯不同類型的需求會非常麻煩。

          如果想寫一個對 int 型數(shù)據(jù)的快速排序,我們編碼為(不是主角,網上隨便找的=_=):

           public static void quickSort(int[] data, int start, int end) {
                  int key = data[start];
                  int i = start;
                  int j = end;
                  while (i < j) {
                      while (data[j] > key && j > i) {
                          j--;
                      }
                      data[i] = data[j];

                      while (data[i] < key && i < j) {
                          i++;
                      }
                      data[j] = data[i];
                  }
                  data[i] = key;

                  if (i - 1 > start) {
                      quickSort(data, start, i - 1);
                  }
                  if (i + 1 < end) {
                      quickSort(data, i + 1, end);
                  }
              }

          可是如果需求變了,現(xiàn)在需要實現(xiàn) int 和 long 兩種數(shù)據(jù)類型的快排,那么我們需要利用 Java 類方法重載功能,復制以上代碼,將參數(shù)類型改為 double 粘貼一遍。可是,如果還要實現(xiàn) float、double 甚至字符串、各種類的快速排序呢,難道每添加一種類型就要復制粘貼一遍代碼嗎,這樣未必太不優(yōu)雅。

          當然我們也可以聲明傳入參數(shù)為 Object,并在比較兩個元素大小時,判斷元素類型,并使用對應的方法比較。這樣,代碼就會惡心在類型判斷上了。不優(yōu)雅的范圍小了一點,并不能解決問題。

          這時,我們考慮使用通用類型(泛型),將快排方法的參數(shù)設置為一個通用類型,無論什么樣的參數(shù),只要實現(xiàn)了 Comparable 接口,都可以傳入并排序。

              public static  <T extends Comparable<T>> void quickSort(T[] data, int start, int end) {
                  T key = data[start];
                  int i = start;
                  int j = end;
                  while (i < j) {
                      while (data[j].compareTo(key) > 0 && j > i) {
                          j--;
                      }
                      data[i] = data[j];

                      while (data[i].compareTo(key) < 0 && i < j) {
                          i++;
                      }
                      data[j] = data[i];
                  }
                  data[i] = key;

                  if (i - 1 > start) {
                      quickSort(data, start, i - 1);
                  }
                  if (i + 1 < end) {
                      quickSort(data, i + 1, end);
                  }
              }

          那么,可以總結一下泛型的應用場景了,當遇到以下場景時,我們可以考慮使用泛型:

          • 當參數(shù)類型不明確,可能會擴展為多種時。
          • 想聲明參數(shù)類型為 Object,并在使用時用 instanceof 判斷時。

          需要注意,泛型只能替代Object的子類型,如果需要替代基本類型,可以使用包裝類,至于為什么,會在下文中說明。

          使用

          然后我們來看一下,泛型怎么用。

          聲明

          泛型的聲明使用 <占位符 [,另一個占位符] > 的形式,需要在一個地方同時聲明多個占位符時,使用 , 隔開。占位符的格式并無限制,不過一般約定使用單個大寫字母,如 T 代表類型(type),E 代表元素*(element)等。雖然沒有嚴格規(guī)定,不過為了代碼的易讀性,最好使用前檢查一下約定用法。

          泛型指代一種參數(shù)類型,可以聲明在類、方法和接口上。

          我們最常把泛型聲明在類上:

              class Generics<T> { // 在類名后聲明引入泛型類型
                  private T field;  // 引入后可以將字段聲明為泛型類型

                  public T getField() { // 類方法內也可以使用泛型類型
                      return field;
                  }
              }

          把泛型聲明在方法上時:

              public [static] <T> void testMethod(T arg) { // 訪問限定符[靜態(tài)方法在 static] 后使用 <占位符> 聲明泛型方法后,在參數(shù)列表后就可以使用泛型類型了
                  // doSomething
              }

          最后是在接口中聲明泛型,如上面的快排中,我們使用了 Comparable<T> 的泛型接口,與此類似的還有 Searializable<T> Iterable<T>等,其實在接口中聲明與在類中聲明并沒有什么太大區(qū)別。

          調用

          然后是泛型的調用,泛型的調用和普通方法或類的調用沒有什么大的區(qū)別,如下:

              public static void main(String[] args) {
                  String[] strArr = new String[2];
                  // 泛型方法的調用跟普通方法相同
            Generics.quickSort(strArr, 0, 30 );

            // 泛型類在調用時需要聲明一種精確類型
                  Generics<Long> sample = new Generics<>();
                  Long field = sample.getField();
              }

              // 泛型接口需要在泛型類里實現(xiàn)
              class GenericsImpl<T> implements Comparable<T> {
              @Override
              public int compareTo(T o) {
                  return 0;
              }
          }

          類型擦除

          講泛型不可不提類型擦除,只有明白了類型擦除,才算明白了泛型,也就可以避開使用泛型時的坑。

          由來

          嚴格來說,Java的泛型并不是真正的泛型。Java 的泛型是 JDK1.5 之后添加的特性,為了兼容之前版本的代碼,其實現(xiàn)引入了類型擦除的概念。

          類型擦除指的是:Java 的泛型代碼在編譯時,由編譯器進行類型檢查,之后會將其泛型類型擦除掉,只保存原生類型,如 Generics<Long> 被擦除后是 Generics,我們常用的 List<String> 被擦除后只剩下 List

          接下來的 Java 代碼在運行時,使用的還是原生類型,并沒有一種新的類型叫 泛型。這樣,也就兼容了泛型之前的代碼。

          如以下代碼:

              public static void main(String[] args) {
                  List<String> stringList = new ArrayList<>();
                  List<Long> longList = new ArrayList<>();

                  if (stringList.getClass() == longList.getClass()) {
                      System.out.println(stringList.getClass().toString());
                      System.out.println(longList.getClass().toString());
             System.out.println("type erased");
                  }
              }

          結果 longList 和 stringList 輸出的類型都為 class java.util.ArrayList,兩者類型相同,說明其泛型類型被擦除掉了。

          實際上,實現(xiàn)了泛型的代碼的字節(jié)碼內會有一個 signature 字段,其中指向了常量表中泛型的真正類型,所以泛型的真正類型,還可以通過反射獲取得到。

          實現(xiàn)

          那么類型擦除之后,Java 是如何保證泛型代碼執(zhí)行期間沒有問題的呢?

          我們將一段泛型代碼用 javac 命令編譯成 class 文件后,再使用 javap 命令查看其字節(jié)碼信息:

          我們會發(fā)現(xiàn),類型里的 T 被替換成了 Object 類型,而在 main 方法里 getField 字段時,進行了類型轉換(checkcast),如此,我們可以看出來 Java 的泛型實現(xiàn)了,一段泛型代碼的編譯運行過程如下:

          1. 編譯期間編譯器檢查傳入的泛型類型與聲明的泛型類型是否匹配,不匹配則報出編譯器錯誤;
          2. 編譯器執(zhí)行類型擦除,字節(jié)碼內只保留其原始類型;
          3. 運行期間,再將 Object 轉換為所需要的泛型類型。

          也就是說:Java 的泛型實際上是由編譯器實現(xiàn)的,將泛型類型轉換為 Object 類型,在運行期間再進行狀態(tài)轉換

          實踐問題

          由上,我們來看使用泛型時需要注意的問題:

          具體類型須為Object子類型

          上文中提到實現(xiàn)泛型時聲明的具體類型必須為 Object 的子類型,這是因為編譯器進行類型擦除后會使用 Object 替換泛型類型,并在運行期間進行類型轉換,而基礎類型和 Object 之間是無法替換和轉換的。

          如:Generics<int> generics = new Generics<int>(); 在編譯期間就會報錯的。

          邊界限定通配符的使用

          泛型雖然為通用類型,但也是可以設置其通用性的,于是就有了邊界限定通配符,而邊界通配符要配合類型擦除才好理解。

          <? extends Generics> 是上邊界限定通配符,避開 上邊界 這個比較模糊的詞不談,我們來看其聲明 xx extends Generics, XX 是繼承了 Generics 的類(也有可能是實現(xiàn),下面只說繼承),我們按照以下代碼聲明:

          List<? extends Generics> genericsList = new ArrayList<>();
          Generics generics = genericsList.get(0);
          genericsList.add(new Generics<String>()); // 編譯無法通過

          我們會發(fā)現(xiàn)最后一行編譯報錯,至于為什么,可以如此理解:XX 是繼承了 Generics 的類,List 中取出來的類一定是可以轉換為 Generics,所以 get 方法沒問題;而具體是什么類,我們并不知道,將父類強制轉換成子類可能會造成運行期錯誤,所以編譯器不允許這種情況;

          而同理 <? super Generics> 是下邊界限定通配符, XX 是 Generics 的父類,所以:

          List<? super Generics> genericsList = new ArrayList<>();
          genericsList.add(new Generics()); // 編譯無法通過
          Generics generics = genericsList.get(0);

          使用前需要根據(jù)這兩種情況,考慮需要 get 還是 set, 進而決定用哪種邊界限定通配符。

          最佳實踐

          當然,泛型并不是一個萬能容器。什么類型都往泛型里扔,還不如直接使用 Object 類型。

          什么時候確定用泛型,如何使用泛型,這些問題的解決不僅僅只依靠編程經驗,我們使用開頭快排的例子整理一下泛型的實踐方式:

          1. 將代碼邏輯拆分為兩部分:通用邏輯和類型相關邏輯;通用邏輯是一些跟參數(shù)類型無關的邏輯,如快排的元素位置整理等;類型相關邏輯,顧名思義,是需要確定類型后才能編寫的邏輯,如元素大小的比較,String 類型的比較和 int 類型的比較就不一樣。
          2. 如果沒有類型相關的邏輯,如 List 作為容器不需要考慮什么類型,那么直接完善通用代碼即可。
          3. 如果有參數(shù)類型相關的邏輯,那么就需要考慮這些邏輯是否已有共同的接口實現(xiàn),如果已有共同的接口實現(xiàn),可以使用邊界限定通配符。如快排的元素就實現(xiàn)了 Compare 接口,Object 已經實現(xiàn)了 toString() 方法,所有的打印語句都可以調用它。
          4. 如果還沒有共同的接口,那么需要考慮是否可以抽象出一個通用的接口實現(xiàn),如打印人類的衣服顏色和動物的毛皮顏色,就可以抽象出一個 getColor() 接口,抽象之后再使用邊界限定通配符。
          5. 如果無法抽象出通用接口,如輸出人類身高或動物體重這種,還是不要使用泛型了,因為不限定類型的話,具體類型的方法調用也就無從談起,編譯也無法通過。

          我將以上步驟整理了一個流程圖,按照這個圖,我們可以快速得出能不能用泛型,怎么用泛型。

          小結

          好好理了一下泛型,感覺收獲頗多,Java 迷霧被撥開了一些。這些特性確實挺難纏,每當自己覺得已經理解得差不多的時候,過些日子又覺得當初理解得還不夠。重要的還是要實踐,在使用時會很容易發(fā)現(xiàn)疑惑的地方。

          來源:https://zhenbianshu.github.io/
          — 【 THE END 】—
          本公眾號全部博文已整理成一個目錄,請在公眾號里回復「m」獲?。?/span>

          最近面試BAT,整理一份面試資料Java面試BATJ通關手冊,覆蓋了Java核心技術、JVM、Java并發(fā)、SSM、微服務、數(shù)據(jù)庫、數(shù)據(jù)結構等等。

          獲取方式:點“在看”,關注公眾號并回復 PDF 領取,更多內容陸續(xù)奉上。

          文章有幫助的話,在看,轉發(fā)吧。

          謝謝支持喲 (*^__^*)

          瀏覽 47
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  欧美日韩性爱视频 | 一级色情视频 | 秋霞网址| 日韩在线免费 | 亚洲欧美中文日韩在线观看 |