<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好幾年,還是不太理解泛型?

          共 10193字,需瀏覽 21分鐘

           ·

          2024-06-15 17:21

          點擊關(guān)注公眾號,Java干貨及時送達

          大部分語言都支持泛型,泛型是一種語言機制,各種語言的實現(xiàn)機制都不太一樣,例如C++使用模板方式來實現(xiàn)泛型,而 Java 中用類型擦除機制來實現(xiàn)泛型。

          什么是泛型

          在 Java 中,不會泛型,寸步難行。泛型可能是一個 Java 初學(xué)者需要攻克的第一個難點。隨便跟著一門教程或 任何一本《Java入門到精通》,前面關(guān)于變量、關(guān)鍵字、語法(if、while、for等等)這些基本上是一看就懂,而當(dāng)內(nèi)容來到泛型的時候,大部分人可能就突然感覺沒那么輕松了。

          如果沒有編程經(jīng)驗的話,可能需要練習(xí)一段時間才能完全掌握泛型編程概念和技巧,這么說吧,有些人寫了好幾年代碼,碰到泛型的時候可能還是不太熟練。

          說到Java泛型,最明顯的標(biāo)志就是 <>

          泛型是什么呢?通俗的說就是一個類型是沒有固定類型的,即可以是Integer 也可以是 Long,還可能是你自定義的類。

          泛型使類型(類和接口)能夠在定義類、接口和方法時成為參數(shù)。與方法聲明中使用的更熟悉的形式參數(shù)非常相似,類型參數(shù)為您提供了一種通過不同輸入重復(fù)使用相同代碼的方法。區(qū)別在于形式參數(shù)的輸入是值,而類型參數(shù)的輸入是類型。

          例如在類定義中使用泛型,最常見的 ArrayList

          public class ArrayList<Eextends AbstractList<E>
                  implements List<E>, RandomAccessCloneablejava.io.Serializable
          {
          //... code
          }

          例如在方法參數(shù)中使用泛型,來一個復(fù)雜的例子

          public static <T extends Number & Comparable<T>, U extends List<T>, R extends T> complexMethod(U list, T element) {

          }

          在這個例子中,有兩個傳入?yún)?shù) U list, T element,而這兩個參數(shù)需要在方法的返回類型前用<>做出說明,也就是 <T extends Number & Comparable<T>, U extends List<T>, R extends T>這一部分。

          返回值也是一個泛型 R

          為什么是 T、U、R

          經(jīng)常看到泛型類型用 T、U、R,還有K、V 這樣的符號表示。我們肯定知道不用T也完全沒問題,用 X 也可以。

          之所以這么統(tǒng)一是因為這是官方比較推薦的寫法,推薦的規(guī)則如下:

          • E - 表示一個元素,例如集合元素、數(shù)組元素
          • K - 表示一個 Key,鍵值對經(jīng)常用到,與之對應(yīng)的是 V
          • V - 表示一個 Value,鍵值對經(jīng)常用
          • N - 表示 Number(數(shù)字類型)
          • T - 這個見得最多,表示一個類型 Type,不管是基礎(chǔ)類型還是自定義的類

          泛型的作用

          前面也說了,當(dāng)一個參數(shù)逾期可能有多種類型的時候,就會用到泛型,那既然是類型不確定,那直接用 Object 不就行了嗎,何必費事兒呢?一會兒講到類型擦除的時候會發(fā)現(xiàn),本身類型擦除的核心就是把泛型類型轉(zhuǎn)為 Object。但是這是編譯器干的,為了給JVM看的。而作為開發(fā)者和編譯器,使用泛型還是有很大好處的。

          1、在編譯時提供更嚴格的類型檢查,如果代碼違反類型安全,編譯器可以及時發(fā)現(xiàn),而不是等到運行的時候拋出運行時異常。

          2、使程序員能夠?qū)崿F(xiàn)通用算法。通過使用泛型,程序員可以實現(xiàn)適用于不同類型集合的泛型算法,可以自定義,并且類型安全且更易于閱讀。

          例如下面這個方法,只接受Number 類型的參數(shù),用來比較兩數(shù)的大小。

           public static <T extends Number> Boolean compare(T first, T second) {
                  double firstValue = first.doubleValue();
                  double secondValue = second.doubleValue();

                  return firstValue > secondValue;
              }

          3、消除不必要的類型轉(zhuǎn)換。

          例如下面不用泛型的情況,每次取數(shù)據(jù)的時候都要轉(zhuǎn)換一下類型。

          List list = new ArrayList();
          list.add("hello");
          String s = (String) list.get(0);

          而用了泛型后,就不用自己轉(zhuǎn)換了。

          List<String> list = new ArrayList<String>();
          list.add("hello");
          String s = list.get(0);

          類型擦除

          Java 中的泛型實現(xiàn)可以說就是用的類型擦除原理。通俗一點說,類型只在編譯期存在,在運行時就不在了,都變?yōu)榱?Object,一視同仁。

          在我們寫好代碼進行編譯時,編譯器會將泛型參數(shù)的類型進行替換,大部分情況下會將類型替換為 0bject 類型。這種行為模式用類型擦除來描述就非常形象。

          類型擦除原理

          在類型擦除過程中,Java 編譯器會擦除所有類型參數(shù),如果類型參數(shù)有界,則用其第一個邊界替換每個參數(shù);如果類型參數(shù)無界,則用 Object 替換。

          在類型擦除過程中,編譯器會按照以下規(guī)則來處理泛型類型參數(shù):

          如果類型參數(shù)有界(bounded type),即使用了extends關(guān)鍵字限定了類型的上界,例如<T extends Number>,則編譯器會用該類型的第一個邊界來替換類型參數(shù)。

          例如下面這個例子,泛型 T 繼承了Number類型,又實現(xiàn)了 Displayable 接口(沒錯,泛型可以這樣定義)


          interface Displayable {
              void display();
          }

          public class Result<T extends Number & Displayable{
              private T value;

              public Result(T value) {
                  this.value = value;
              }

              public T getValue() {
                  return value;
              }
              
           public void show() {
                  value.display();
              }
          }

          在編譯器進行類型擦除后會變成下面這樣,因為 T 的上限是 Number,所以直接將 T 替換為 Number。

          public class Result {
              private Number value;

              public Result(Number value) {
                  this.value = value;
              }

              public Number getValue() {
                  return value;
              }
          }

          如果類型參數(shù)無界(unbounded type),即沒有限定類型的上界,例如<T>,則編譯器會用Object類型來替換類型參數(shù)。

          例如下面方法,沒有指定類型上限類型。

          public static <T> int count(T[] anArray, T elem) {
              int cnt = 0;
              for (T e : anArray)
                  if (e.equals(elem))
                      ++cnt;
                  return cnt;
          }

          經(jīng)過編譯器的擦除處理后,就變成下面這樣,都替換成了 Object。

          public static int count(Object[] anArray, Object elem) {
              int cnt = 0;
              for (Object e : anArray)
                  if (e.equals(elem))
                      ++cnt;
                  return cnt;
          }

          橋接方法

          來看一下下面這段代碼

          public class Node<T{

              public T data;

              public Node(T data) this.data = data; }

              public void setData(T data) {
                  this.data = data;
              }
          }

          public class SubNode extends Node<Integer{
              public SubNode(Integer data) super(data); }

              public void setData(Integer data) {
                  super.setData(data);
              }
              
           public static void main(String[] args) {
                  SubNode subNode = new SubNode(8);
                  Node node = subNode;
                  node.setData("Hello");
                  Integer x = subNode.data;
              }

          }

          這段代碼大家一看就知道肯定是有問題的,運行的時候會出現(xiàn) ClassCastException,但是編譯是可以通過的。

          而運行時出現(xiàn)錯誤的代碼是 node.setData("Hello");這一行,但是經(jīng)過前面對類型擦除的了解,Node 類的 setData 參數(shù)肯定被擦除成了 Object 類型了,既然是 Object,那Integer 和 String 都滿足啊,為啥還會報錯呢。

          這就要說到橋接了。

          當(dāng)編譯器對泛型擴展的類或接口進行編譯處理的時候,會根據(jù)實際的類型進行方法的橋接處理。什么意思呢,還是拿上面的 Node 和 SubNode 類說明。

          類型擦除后的代碼是下面這樣的,多了一個橋接方法。

          public class Node {

              public Object data;

              public Node(Object data) this.data = data; }

              public void setData(Object data) {
                  this.data = data;
              }
          }

          public class SubNode extends Node {

              public SubNode(Integer data) super(data); }

           /**
           ** 橋接方法
           **/

           public void setData(Object data) {
                  setData((Integer) data);
              }

              public void setData(Integer data) {
                  super.setData(data);
              }
          }

          為什么需要這個橋接方法呢?

          Node 類的 setData 方法入?yún)⑹?Object 類型。

          public void setData(Object data) {
              this.data = data;
          }

          而 SubNode 的setData 方法入?yún)⑹?Integer。

          public void setData(Integer data) {
           super.setData(data);
          }

          所以,SubNode 的 setData 方法并不會重寫父類 Node 的setData 方法,而想要重寫的話,就必須讓 SubNode 的setData 的入?yún)⒁彩?Object,這就是橋接方法的由來。

          public void setData(Object data) {
           setData((Integer) data);
          }

          這樣一來重寫父類的方法,但是要把參數(shù)強轉(zhuǎn)成 Integer。

          前面說的 node.setData("Hello");這一行會報錯,那大家就知道為什么了吧,是因為把 Hello強轉(zhuǎn)為 Integer 的時候出現(xiàn)的錯誤。

          總結(jié)

          正是類型擦除的機制幫助 Java 實現(xiàn)了泛型編程,讓我們作為開發(fā)者能夠更好的了解和控制我們正在使用類型的是什么,而不是 Object 滿天飛。

              

            

                 

                  

          1、前MySQL工程師發(fā)聲:再不悔改,Oracle將殺死MySQL!

          2、首款配備驍龍X Elite處理器的Linux筆記本:采用KDE Plasma桌面環(huán)境、計劃年底推出

          3、解決日志開關(guān)需求:SpringBoot 實現(xiàn)熱插拔 AOP

          4、微軟不再允許Win11通過[email protected]繞過登錄 但還有其他辦法可以繼續(xù)用

          5、相比高人氣的Rust、Go,為何 Java、C 在工具層面進展緩慢?

          6、別再手動拼接 SQL 了,MyBatis 動態(tài) SQL 寫法應(yīng)有盡有,建議收藏!

          點在看

          瀏覽 132
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  亚洲三级播放 | 天堂sV在线最新版在线 | 麻豆成人在线观看 | 黄色A a | 成人福利免费视频 |