Java 泛型背後的原理是什麼?

2021-02-18 網際網路架構師

舉一個簡單的例子:

這裡可以看出來在代碼編寫階段就已經報錯了,不能往string類型的集合中添加int類型的數據。

那可不可以往List集合中添加多個類型的數據呢,答案是可以的,其實我們可以把list集合當成普通的類也是沒問題的,那麼就有下面的代碼:

從這裡可以看出來,不定義泛型也是可以往集合中添加數據的,所以說泛型只是一種類型的規範,在代碼編寫階段起一種限制。

下面我們通過例子來介紹泛型背後數據是什麼類型

public class BaseBean<T> {
    T value;

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}

上面定義了一個泛型的類,然後我們通過反射獲取屬性和getValue方法返回的數據類型:從日誌上看到通過反射獲取到的屬性是Object類型的,在方法中返回的是string類型,因此咋們可以思考在getValue方法裡面實際是做了個強轉的動作,將object類型的value強轉成string類型。

大家有沒有想過為啥要用泛型呢,既然說了泛型其實對於jvm來說都是Object類型的,那咱們直接將類型定義成Object不就是的了,這種做法是可以,但是在拿到Object類型值之後,自己還得強轉,因此泛型減少了代碼的強轉工作,而將這些工作交給了虛擬機。 

勢必在getValue的時候代碼有個強轉的過程,因此在能用泛型的時候,儘量用泛型來寫,而且我認為一個好的架構師,業務的抽取是離不開泛型的定義。

public class BaseBean<T> {
    public String errMsg;
    public T data;
    public int status;
}

//抽象類泛型
public abstract class BaseAdapter<T> {
    List<T> DATAS;}//接口泛型public interface Factory<T> {
    T create();
}
//方法泛型
public static <T> T getData() {
    return null;
}

public interface Base<K, V> {
    void setKey(K k);

    V getValue();}

public interface BaseCommon<K extends Common1, V> extends Base<K, V> {
}

//或抽象類
public abstract class BaseCommon<K extends Common1, V> 
implements Base<K, V> {
}

public interface Base<K, V> {
   //    void setKey(K k);////    V getValue();
   void addNode(Map<K, V> map);

   Map<K, V> getNode(int index);}public abstract class BaseCommon<K, V> implements Base<K, V> {
   //多重泛型
   LinkedList<Map<K, V>> DATAS = new LinkedList<>();

   @Override
   public void addNode(Map<K, V> map) {
       DATAS.addLast(map);
   }

   @Override
   public Map<K, V> getNode(int index) {
       return DATAS.get(index);
   }
}

通配符  
<?>通配符和<T>區別是在你不知道泛型類型的時候,可以用通配符來定義,下面通過一個例子來看看的用處:

//定義了一個普通類
public class BaseBean<T> {
    T value;

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}

//用來定義泛型的
public class Common1 extends Common {
}

在定義的時候將Common的泛型指向Common1的泛型,可以看到直接提示有問題,這裡可以想,雖然Common1是繼承自Common的,但是並不代表BaseBean之間是等量的。

在開篇也講過,如果泛型傳入的是什麼類型,那麼在BaseBean中的getValue返回的類型就是什麼,因此可以想兩個不同的泛型類肯定是不等價的,但是如果我這裡寫呢:

public static void main(String\[\] args) {
    BaseBean<Common> commonBaseBean = new BaseBean<>();
    //通配符定義就沒有問題
    BaseBean<?> common1BaseBean = commonBaseBean;
    try {
        //通過反射猜測setValue的參數是Object類型的
        Method setValue = common1BaseBean.getClass().getDeclaredMethod("setValue", Object.class);
        setValue.invoke(common1BaseBean, "123");
        Object value = common1BaseBean.getValue();
        System.out.println("result:" + value);
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    }
}

在上面如果定義的泛型是通配符是可以等價的,因為此時的setValue的參數是Object類型,所以能直接將上面定義的泛型賦給通配符的BaseBean。另外,關注微信公眾號:網際網路架構師,在後臺回覆:2T,可以獲取架構師視頻教程,都是乾貨。

通配符不能定義在類上面、接口或方法上,只能作用在方法的參數上  

其他的幾種情況自己去嘗試,正確的使用通配符:

public void setClass(Class<?> class){
    //todo
}

<T extends>、<T super>、<? extends>、<? super>
<T extends>表示上限泛型、<T super>表示下限泛型  

//新增加的一個
BaseCommonpublic class Common extends BaseCommon{}


第二個定義的泛型是不合法的,因為BaseCommon是Common的父類,超出了Common的類型範圍。10 道泛型面試題,推薦你看下。

public void add(Class<? super Common> clazz) {}


可以看到當傳進去的是Common1.class的時候是不合法的,因為在add方法中需要傳入Common父類的字節碼對象,而Common1是繼承自Common,所以直接不合法。在實際開發中其實知道什麼時候定義什麼類型的泛型就ok,在mvp實際案例中泛型用得比較廣泛,大家可以根據實際項目來找找泛型的感覺,只是面試的時候需要理解類型擦除是針對誰而言的。關注微信公眾號:網際網路架構師,獲取更多架構技術乾貨。

類型擦除  

其實在開篇的時候已經通過例子說明了,通過反射繞開泛型的定義,也說明了類中定義的泛型最終是以Object被jvm執行。所有的泛型在jvm中執行的時候,都是以Object對象存在的,加泛型只是為了一種代碼的規範,避免了開發過程中再次強轉。 泛型信息只存在於代碼編譯階段,在進入 JVM 之前,與泛型相關的信息會被擦除掉,專業術語叫做類型擦除。作者:的一幕  
https://www.jianshu.com/p/dd34211f2565

相關焦點

  • Java 泛型背後的原理
    作者 | 的一幕  來源 | https://www.jianshu.com/p/dd34211f2565這一節主要講的內容是java中泛型的應用,通過該篇讓大家更好地理解泛型,以及面試中經常說的泛型類型擦除是什麼概念,今天就帶著這幾個問題一起看下:舉一個簡單的例子:這裡可以看出來在代碼編寫階段就已經報錯了,不能往string類型的集合中添加int類型的數據。
  • Java泛型背後是什麼
    這一節主要講的內容是java中泛型的應用,通過該篇讓大家更好地理解泛型,以及面試中經常說的泛型類型擦除是什麼概念,今天就帶著這幾個問題一起看下:舉一個簡單的例子:這裡可以看出來在代碼編寫階段就已經報錯了,不能往string類型的集合中添加int類型的數據。
  • Java之使用泛型與未使用泛型的區別
    各位小夥伴們大家好,在之前的文章中,小編有介紹過Java之泛型的概念,這次小編要介紹的是,Java程序中,使用泛型與未使用泛型的區別。代碼如下:public class Demo01Generic {public static void main(String[] args) { show01(); show02(); } /*創建集合對象,使用泛型好處:1.避免了類型轉換的麻煩,存儲的是什麼類型的數據,取出的就是什麼類型的數據 2.把運行期異常
  • Java總結篇系列:Java泛型
    泛型概念的提出(為什麼需要泛型)?因為編譯階段正常,而運行時會出現「java.lang.ClassCastException」異常。因此,導致此類錯誤編碼過程中不易發現。在如上的編碼過程中,我們發現主要存在兩個問題:1.當我們將一個對象放入集合中,集合不會記住此對象的類型,當再次從集合中取出此對象時,改對象的編譯類型變成了Object類型,但其運行時類型任然為其本身類型。
  • Java反射,泛型在Json中的運用
    最近項目中遇到了Json數據自動獲取的功能,不然令人想起java的反射,已經很長時間沒複習java了正好一塊連java的這一塊內容一起過一遍。java中的反射無疑就相當於java開發者的春天,在眾多的框架中也能看到它的身影,可以在運行時檢查類,接口、變量和方法等信息,可以實例化調用方法以及設置變量值等。本文主要以代碼的形式直接將反射,泛型的運用展現出來。java中的反射首先新建一個基礎類Author。
  • 泛型Java程式設計師必備基礎
    前言整理了Java泛型的相關知識,算是比較基礎的,希望大家一起學習進步。一、什麼是Java泛型Java 泛型(generics)是 JDK 5 中引入的一個新特性,其本質是參數化類型,解決不確定具體對象類型的問題。
  • Java泛型的特點與優缺點,泛型擦除是怎麼回事?
    這道題想考察什麼?Java 是如何支持泛型的?底層實現?
  • Java泛型總結
    什麼是泛型泛型是jdk5引入的類型機制,就是將類型參數化,它是早在1999年就制定的jsr14的實現。泛型機制將類型轉換時的類型檢查從運行時提前到了編譯時,使用泛型編寫的代碼比雜亂的使用object並在需要時再強制類型轉換的機制具有更好的可讀性和安全性。
  • Java泛型解析,了解泛型使用
    Java 泛型泛型的本質是參數化類型。簡單解釋就是,將參數的數據類型也當作參數對待。泛型的目的就是為了寫一套代碼,可以到處通用,不用擔心類型安全的問題。泛型可以用在類、接口、方法中對應的就是泛型類、泛型接口和泛型方法。一、為什麼要引入泛型?
  • 3 分鐘帶你徹底搞懂 Java 泛型背後的秘密
    作者 | 的一幕來源 | www.jianshu.com/p/dd34211f2565這一節主要講的內容是java中泛型的應用
  • Java 泛型詳解-絕對是對泛型方法講解最詳細的,沒有之一
    本文參考java 泛型詳解、Java中的泛型方法、 java泛型詳解概述泛型在java中有很重要的地位,在面向對象編程及各種設計模式中有非常廣泛的應用。什麼是泛型?為什麼要使用泛型?泛型,即「參數化類型」。一提到參數,最熟悉的就是定義方法時有形參,然後調用此方法時傳遞實參。那麼參數化類型怎麼理解呢?
  • JAVA總結篇系列-泛型
    泛型概念的提出(為什麼需要泛型)?2.因此,//1處取出集合元素時需要人為的強制類型轉化到具體的目標類型,且很容易出現「java.lang.ClassCastException」異常。那麼有沒有什麼辦法可以使集合能夠記住集合內元素各類型,且能夠達到只要編譯時不出現問題,運行時就不會出現「java.lang.ClassCastException」異常呢?答案就是使用泛型。
  • 為什麼我們需要Java中的泛型類型?
    泛型類型在Java集合中廣泛使用。為什麼我們需要Java中的泛型類型?理解這個問題可以幫助我們更好地理解許多相關概念。在本文中,我將使用一個非常簡短的示例來說明Generic為什麼有用。1.泛型概述實現泛型的目的是在編譯時而不是運行時中發現錯誤。在編譯時查找錯誤可以節省調試Java程序的時間,因為編譯時錯誤更容易查找和修復。泛型類型僅在編譯時存在。這是學習Java泛型時要記住的最重要的事情。2.如果沒有泛型怎麼辦?
  • Java的「泛型」特性,你以為自己會了?(萬字長文)
    - (表示不確定的java類型)但是泛型的參數只能是類類型,不能是基本的數據類型,他的類型一定是自Object的注意:泛型不接受基本數據類型,換句話說,只有引用類型才能作為泛型方法的實際參數2. 為什麼要使用泛型?
  • Java基礎教程:Java泛型概述及應用(Java難點)
    就要把迭代出來的對象轉成String類型 String str = (String) it.next(); System.out.println(str.length()); }}}程序在運行時發生了問題java.lang.ClassCastException
  • Java泛型概念相關面試題匯總.
    來源:http://1t.click/G2m問:Java 的泛型是什麼?有什麼好處和優點?JDK 不同版本的泛型有什麼區別?正是因為這種兼容也帶來了一些代價,譬如泛型不能顯式地引用運行時類型的操作之中(如向上向下轉型、instanceof 操作等),因為所有關於參數的信息都丟失了,所以任何時候使用泛型都要提醒自己背後的真實擦除類型到底是什麼;此外擦除和兼容性導致了使用泛型並不是強制的(如 List list = new ArrayList(); 等寫法);其次擦除會導致我們在編寫代碼時十分謹慎(如不想被擦除為 Object
  • Java 泛型 T,E,K,V,?,傻傻分不清?
    # 泛型中通配符 我們在定義泛型類,泛型方法,泛型接口的時候經常會碰見很多不同的通配符,比如 T,E,K,V 等等,這些通配符又都是什麼意思呢? 常用的 T,E,K,V,? 本質上這些個都是通配符,沒啥區別,只不過是編碼時的一種約定俗成的東西。
  • Java 泛型詳解
    看完了泛型類,接下來我們來了解一下泛型方法。類型擦除就是說Java泛型只能用於在編譯期間的靜態類型檢查,然後編譯器生成的代碼會擦除相應的類型信息,這樣到了運行期間實際上JVM根本就知道泛型所代表的具體類型。這樣做的目的是因為Java泛型是1.5之後才被引入的,為了保持向下的兼容性,所以只能做類型擦除來兼容以前的非泛型代碼。對於這一點,如果閱讀Java集合框架的源碼,可以發現有些類其實並不支持泛型。
  • java泛型的基本使用案例,重基礎
    持續更新中……在上幾篇圖文中已經介紹了容器集合的大部分的基本使用,接下來按照知識圖譜的順序來介紹一下java泛型的使用。在下面的例子中講創建一個繼承結構,然後用基本類型的類型來聲明容器,看看容器表現是否表現正常,並且創建一個泛型方法,來觀察對類型的處理。下面通過代碼展示:
  • Java基礎-今日內容介紹(集合、Iterator、增強for循環 泛型)
    1.集合知識點---集合的概念ArrayList集合的使用集合存儲任意數據類型,不指定數據類型,集合什麼都存,但是存在安全問題泛型就是需要我們指定存儲的數據類型。Java的泛型是偽泛型,編譯後的文件裡是沒有泛型的。