<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這個(gè)高級(jí)特性,很多人還沒(méi)用過(guò)!

          共 11299字,需瀏覽 23分鐘

           ·

          2021-05-29 10:24

          點(diǎn)擊“開(kāi)發(fā)者技術(shù)前線(xiàn)”,選擇“星標(biāo)??”

          讓一部分開(kāi)發(fā)者看到未來(lái)

          來(lái)源 | https://zhenbianshu.github.io/
          泛型是 Java 的高級(jí)特性之一,如果想寫(xiě)出優(yōu)雅而高擴(kuò)展性的代碼,或是想讀得懂一些優(yōu)秀的源碼,泛型是繞不開(kāi)的檻。本文介紹了什么是泛型、類(lèi)型擦除的概念及其實(shí)現(xiàn),最后總結(jié)了泛型使用的最佳實(shí)踐。

          前言


          想寫(xiě)一下關(guān)于 Java 一些高級(jí)特性的文章,雖然這些特性在平常實(shí)現(xiàn)普通業(yè)務(wù)時(shí)不必使用,但如果想寫(xiě)出優(yōu)雅而高擴(kuò)展性的代碼,或是想讀得懂一些優(yōu)秀的源碼,這些特性又是不可避免的。
          如果對(duì)這些特性不了解,不熟悉特性的應(yīng)用場(chǎng)景,使用時(shí)又因?yàn)檎Z(yǔ)法等原因困難重重,很難讓人克服惰性去使用它們,所以身邊總有一些同事,工作了很多年,卻從沒(méi)有用過(guò) Java 的某些高級(jí)特性,寫(xiě)出的代碼總是差那么一點(diǎn)兒感覺(jué)。
          為了避免幾年后自己的代碼還是非常 low,我準(zhǔn)備從現(xiàn)在開(kāi)始深入理解一下這些特性。本文先寫(xiě)一下應(yīng)用場(chǎng)景最多的泛型。

          泛型是什么


          首先來(lái)說(shuō)泛型是什么。泛型的英文是 generic,中文意思是通用的、一類(lèi)的,結(jié)合其應(yīng)用場(chǎng)景,我理解泛型是一種 通用類(lèi)型。但我們一般指泛型都是指其實(shí)現(xiàn)方式,也就是 將類(lèi)型參數(shù)化
          對(duì)于 Java 這種強(qiáng)類(lèi)型語(yǔ)言來(lái)說(shuō),如果沒(méi)有泛型的話(huà),處理相同邏輯不同類(lèi)型的需求會(huì)非常麻煩。
          如果想寫(xiě)一個(gè)對(duì) int 型數(shù)據(jù)的快速排序,我們編碼為(不是主角,網(wǎng)上隨便找的=_=):
           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)在需要實(shí)現(xiàn) int 和 long 兩種數(shù)據(jù)類(lèi)型的快排,那么我們需要利用 Java 類(lèi)方法重載功能,復(fù)制以上代碼,將參數(shù)類(lèi)型改為 double 粘貼一遍??墒?,如果還要實(shí)現(xiàn) float、double 甚至字符串、各種類(lèi)的快速排序呢,難道每添加一種類(lèi)型就要復(fù)制粘貼一遍代碼嗎,這樣未必太不優(yōu)雅。
          當(dāng)然我們也可以聲明傳入?yún)?shù)為 Object,并在比較兩個(gè)元素大小時(shí),判斷元素類(lèi)型,并使用對(duì)應(yīng)的方法比較。這樣,代碼就會(huì)惡心在類(lèi)型判斷上了。不優(yōu)雅的范圍小了一點(diǎn),并不能解決問(wèn)題。
          這時(shí),我們考慮使用通用類(lèi)型(泛型),將快排方法的參數(shù)設(shè)置為一個(gè)通用類(lèi)型,無(wú)論什么樣的參數(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);
                  }
              }

          那么,可以總結(jié)一下泛型的應(yīng)用場(chǎng)景了,當(dāng)遇到以下場(chǎng)景時(shí),我們可以考慮使用泛型:
          • 當(dāng)參數(shù)類(lèi)型不明確,可能會(huì)擴(kuò)展為多種時(shí)。
          • 想聲明參數(shù)類(lèi)型為 Object,并在使用時(shí)用 instanceof 判斷時(shí)。
          需要注意,泛型只能替代Object的子類(lèi)型,如果需要替代基本類(lèi)型,可以使用包裝類(lèi),至于為什么,會(huì)在下文中說(shuō)明。

          使用


          然后我們來(lái)看一下,泛型怎么用。

          聲明

          泛型的聲明使用 <占位符 [,另一個(gè)占位符] > 的形式,需要在一個(gè)地方同時(shí)聲明多個(gè)占位符時(shí),使用 , 隔開(kāi)。占位符的格式并無(wú)限制,不過(guò)一般約定使用單個(gè)大寫(xiě)字母,如 T 代表類(lèi)型(type),E 代表元素*(element)等。雖然沒(méi)有嚴(yán)格規(guī)定,不過(guò)為了代碼的易讀性,最好使用前檢查一下約定用法。
          泛型指代一種參數(shù)類(lèi)型,可以聲明在類(lèi)、方法和接口上。
          我們最常把泛型聲明在類(lèi)上:
              class Generics<T// 在類(lèi)名后聲明引入泛型類(lèi)型
                  private T field;  // 引入后可以將字段聲明為泛型類(lèi)型

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

          把泛型聲明在方法上時(shí):
              public [static] <T> void testMethod(T arg) // 訪(fǎng)問(wèn)限定符[靜態(tài)方法在 static] 后使用 <占位符> 聲明泛型方法后,在參數(shù)列表后就可以使用泛型類(lèi)型了
                  // doSomething
              }

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

          調(diào)用

          然后是泛型的調(diào)用,泛型的調(diào)用和普通方法或類(lèi)的調(diào)用沒(méi)有什么大的區(qū)別,如下:
              public static void main(String[] args) {
                  String[] strArr = new String[2];
                  // 泛型方法的調(diào)用跟普通方法相同
            Generics.quickSort(strArr, 030 );

            // 泛型類(lèi)在調(diào)用時(shí)需要聲明一種精確類(lèi)型
                  Generics<Long> sample = new Generics<>();
                  Long field = sample.getField();
              }

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

          類(lèi)型擦除


          講泛型不可不提類(lèi)型擦除,只有明白了類(lèi)型擦除,才算明白了泛型,也就可以避開(kāi)使用泛型時(shí)的坑。

          由來(lái)

          嚴(yán)格來(lái)說(shuō),Java的泛型并不是真正的泛型。Java 的泛型是 JDK1.5 之后添加的特性,為了兼容之前版本的代碼,其實(shí)現(xiàn)引入了類(lèi)型擦除的概念。
          類(lèi)型擦除指的是:Java 的泛型代碼在編譯時(shí),由編譯器進(jìn)行類(lèi)型檢查,之后會(huì)將其泛型類(lèi)型擦除掉,只保存原生類(lèi)型,如 Generics<Long> 被擦除后是 Generics,我們常用的 List<String> 被擦除后只剩下 List。
          接下來(lái)的 Java 代碼在運(yùn)行時(shí),使用的還是原生類(lèi)型,并沒(méi)有一種新的類(lèi)型叫 泛型。這樣,也就兼容了泛型之前的代碼。
          如以下代碼:
              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");
                  }
              }

          結(jié)果 longList 和 stringList 輸出的類(lèi)型都為 class java.util.ArrayList,兩者類(lèi)型相同,說(shuō)明其泛型類(lèi)型被擦除掉了。
          實(shí)際上,實(shí)現(xiàn)了泛型的代碼的字節(jié)碼內(nèi)會(huì)有一個(gè) signature 字段,其中指向了常量表中泛型的真正類(lèi)型,所以泛型的真正類(lèi)型,還可以通過(guò)反射獲取得到。

          實(shí)現(xiàn)

          那么類(lèi)型擦除之后,Java 是如何保證泛型代碼執(zhí)行期間沒(méi)有問(wèn)題的呢?
          我們將一段泛型代碼用 javac 命令編譯成 class 文件后,再使用 javap 命令查看其字節(jié)碼信息:
          我們會(huì)發(fā)現(xiàn),類(lèi)型里的 T 被替換成了 Object 類(lèi)型,而在 main 方法里 getField 字段時(shí),進(jìn)行了類(lèi)型轉(zhuǎn)換(checkcast),如此,我們可以看出來(lái) Java 的泛型實(shí)現(xiàn)了,一段泛型代碼的編譯運(yùn)行過(guò)程如下:
          1. 編譯期間編譯器檢查傳入的泛型類(lèi)型與聲明的泛型類(lèi)型是否匹配,不匹配則報(bào)出編譯器錯(cuò)誤;
          2. 編譯器執(zhí)行類(lèi)型擦除,字節(jié)碼內(nèi)只保留其原始類(lèi)型;
          3. 運(yùn)行期間,再將 Object 轉(zhuǎn)換為所需要的泛型類(lèi)型。
          也就是說(shuō):Java 的泛型實(shí)際上是由編譯器實(shí)現(xiàn)的,將泛型類(lèi)型轉(zhuǎn)換為 Object 類(lèi)型,在運(yùn)行期間再進(jìn)行狀態(tài)轉(zhuǎn)換

          實(shí)踐問(wèn)題


          由上,我們來(lái)看使用泛型時(shí)需要注意的問(wèn)題:

          具體類(lèi)型須為Object子類(lèi)型

          上文中提到實(shí)現(xiàn)泛型時(shí)聲明的具體類(lèi)型必須為 Object 的子類(lèi)型,這是因?yàn)榫幾g器進(jìn)行類(lèi)型擦除后會(huì)使用 Object 替換泛型類(lèi)型,并在運(yùn)行期間進(jìn)行類(lèi)型轉(zhuǎn)換,而基礎(chǔ)類(lèi)型和 Object 之間是無(wú)法替換和轉(zhuǎn)換的。
          如:Generics<int> generics = new Generics<int>(); 在編譯期間就會(huì)報(bào)錯(cuò)的。

          邊界限定通配符的使用

          泛型雖然為通用類(lèi)型,但也是可以設(shè)置其通用性的,于是就有了邊界限定通配符,而邊界通配符要配合類(lèi)型擦除才好理解。
          <? extends Generics> 是上邊界限定通配符,避開(kāi) 上邊界 這個(gè)比較模糊的詞不談,我們來(lái)看其聲明 xx extends Generics, XX 是繼承了 Generics 的類(lèi)(也有可能是實(shí)現(xiàn),下面只說(shuō)繼承),我們按照以下代碼聲明:
          List<? extends Generics> genericsList = new ArrayList<>();
          Generics generics = genericsList.get(0);
          genericsList.add(new Generics<String>()); // 編譯無(wú)法通過(guò)

          我們會(huì)發(fā)現(xiàn)最后一行編譯報(bào)錯(cuò),至于為什么,可以如此理解:XX 是繼承了 Generics 的類(lèi),List 中取出來(lái)的類(lèi)一定是可以轉(zhuǎn)換為 Generics,所以 get 方法沒(méi)問(wèn)題;而具體是什么類(lèi),我們并不知道,將父類(lèi)強(qiáng)制轉(zhuǎn)換成子類(lèi)可能會(huì)造成運(yùn)行期錯(cuò)誤,所以編譯器不允許這種情況;
          而同理 <? super Generics> 是下邊界限定通配符, XX 是 Generics 的父類(lèi),所以:
          List<? super Generics> genericsList = new ArrayList<>();
          genericsList.add(new Generics()); // 編譯無(wú)法通過(guò)
          Generics generics = genericsList.get(0);

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

          最佳實(shí)踐

          當(dāng)然,泛型并不是一個(gè)萬(wàn)能容器。什么類(lèi)型都往泛型里扔,還不如直接使用 Object 類(lèi)型。
          什么時(shí)候確定用泛型,如何使用泛型,這些問(wèn)題的解決不僅僅只依靠編程經(jīng)驗(yàn),我們使用開(kāi)頭快排的例子整理一下泛型的實(shí)踐方式:
          1. 將代碼邏輯拆分為兩部分:通用邏輯和類(lèi)型相關(guān)邏輯;通用邏輯是一些跟參數(shù)類(lèi)型無(wú)關(guān)的邏輯,如快排的元素位置整理等;類(lèi)型相關(guān)邏輯,顧名思義,是需要確定類(lèi)型后才能編寫(xiě)的邏輯,如元素大小的比較,String 類(lèi)型的比較和 int 類(lèi)型的比較就不一樣。
          2. 如果沒(méi)有類(lèi)型相關(guān)的邏輯,如 List 作為容器不需要考慮什么類(lèi)型,那么直接完善通用代碼即可。
          3. 如果有參數(shù)類(lèi)型相關(guān)的邏輯,那么就需要考慮這些邏輯是否已有共同的接口實(shí)現(xiàn),如果已有共同的接口實(shí)現(xiàn),可以使用邊界限定通配符。如快排的元素就實(shí)現(xiàn)了 Compare 接口,Object 已經(jīng)實(shí)現(xiàn)了 toString() 方法,所有的打印語(yǔ)句都可以調(diào)用它。
          4. 如果還沒(méi)有共同的接口,那么需要考慮是否可以抽象出一個(gè)通用的接口實(shí)現(xiàn),如打印人類(lèi)的衣服顏色和動(dòng)物的毛皮顏色,就可以抽象出一個(gè) getColor() 接口,抽象之后再使用邊界限定通配符。
          5. 如果無(wú)法抽象出通用接口,如輸出人類(lèi)身高或動(dòng)物體重這種,還是不要使用泛型了,因?yàn)椴幌薅?lèi)型的話(huà),具體類(lèi)型的方法調(diào)用也就無(wú)從談起,編譯也無(wú)法通過(guò)。
          我將以上步驟整理了一個(gè)流程圖,按照這個(gè)圖,我們可以快速得出能不能用泛型,怎么用泛型。

          小結(jié)


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

          —  —

          點(diǎn)這里??關(guān)注我,記得標(biāo)星呀~

          前線(xiàn)推出學(xué)習(xí)交流一定要備注:研究/工作方向+地點(diǎn)+學(xué)校/公司+昵稱(chēng)(如JAVA+上海+上交+可可),根據(jù)格式備注,可更快被通過(guò)且邀請(qǐng)進(jìn)群


          掃碼加小編微信,進(jìn)群和大佬們零距離



          END


          后臺(tái)回復(fù)“電子書(shū)” “資料” 領(lǐng)取一份干貨,數(shù)百面試手冊(cè)等你
          開(kāi)發(fā)者技術(shù)前線(xiàn) ,匯集技術(shù)前線(xiàn)快訊和關(guān)注行業(yè)趨勢(shì),大廠干貨,是開(kāi)發(fā)者經(jīng)歷和成長(zhǎng)的優(yōu)秀指南。

          好文點(diǎn)個(gè)在看吧!
          瀏覽 32
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  一本色道久久综合无码 | ThePorn精品无码 | 中文字幕99 | 日韩精品一区二区三区在线观看 | 国产高清一级 |