色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

您好,歡迎來電子發燒友網! ,新用戶?[免費注冊]

您的位置:電子發燒友網>源碼下載>java源碼下載>

聊聊java泛型實現的原理與好處

大小:0.9 MB 人氣:0 2017-09-27 需要積分:1

  摘要: 和C++以模板來實現靜多態不同,Java基于運行時支持選擇了泛型,兩者的實現原理大相庭徑。C++可以支持基本類型作為模板參數,Java卻只能接受類作為泛型參數;Java可以在泛型類的方法中取得自己泛型參數的Class類型,C++只能由編譯器推斷在不為人知的地方生成新的類,對于特定的模板參數你只能使用特化。在本文中我主要想聊聊泛型的實現原理和一些高級特性。

  泛型基礎

  泛型是對Java語言類型系統的一種擴展,有點類似于C++的模板,可以把類型參數看作是使用參數化類型時指定的類型的一個占位符。引入泛型,是對Java語言一個較大的功能增強,帶來了很多的好處:

  類型安全。類型錯誤現在在編譯期間就被捕獲到了,而不是在運行時當作java.lang.ClassCastException展示出來,將類型檢查從運行時挪到編譯時有助于開發者更容易找到錯誤,并提高程序的可靠性

  消除了代碼中許多的強制類型轉換,增強了代碼的可讀性

  為較大的優化帶來了可能

  泛型是什么并不會對一個對象實例是什么類型的造成影響,所以,通過改變泛型的方式試圖定義不同的重載方法是不可以的。剩下的內容我不會對泛型的使用做過多的講述,泛型的通配符等知識請自行查閱。

  在進入下面的論述之前我想先問幾個問題:

  定義一個泛型類最后到底會生成幾個類,比如ArrayList到底有幾個類

  定義一個泛型方法最終會有幾個方法在class文件中

  為什么泛型參數不能是基本類型呢

  ArrayList是一個類嗎

  ArrayList和List和ArrayList和List是什么關系呢,這幾個類型的引用能相互賦值嗎

  類型擦除

  正確理解泛型概念的首要前提是理解類型擦除(type erasure)。 Java中的泛型基本上都是在編譯器這個層次來實現的。在生成的Java字節代碼中是不包含泛型中的類型信息的。使用泛型的時候加上的類型參數,會被編譯器在編譯的時候去掉。這個過程就稱為類型擦除。如在代碼中定義的List和List等類型,在編譯之后都會變成List。JVM看到的只是List,而由泛型附加的類型信息對JVM來說是不可見的。Java編譯器會在編譯時盡可能的發現可能出錯的地方,但是仍然無法避免在運行時刻出現類型轉換異常的情況。類型擦除也是Java的泛型實現方式與C++模板機制實現方式之間的重要區別。

  很多泛型的奇怪特性都與這個類型擦除的存在有關,包括:

  泛型類并沒有自己獨有的Class類對象。比如并不存在List.class或是List.class,而只有List.class。

  靜態變量是被泛型類的所有實例所共享的。對于聲明為MyClass的類,訪問其中的靜態變量的方法仍然是 MyClass.myStaticVar。不管是通過new MyClass還是new MyClass創建的對象,都是共享一個靜態變量。

  泛型的類型參數不能用在Java異常處理的catch語句中。因為異常處理是由JVM在運行時刻來進行的。由于類型信息被擦除,JVM是無法區分兩個異常類型MyException和MyException的。對于JVM來說,它們都是 MyException類型的。也就無法執行與異常對應的catch語句。

  類型擦除的基本過程也比較簡單,首先是找到用來替換類型參數的具體類。這個具體類一般是Object。如果指定了類型參數的上界的話,則使用這個上界。把代碼中的類型參數都替換成具體的類。同時去掉出現的類型聲明,即去掉《》的內容。比如T get()方法聲明就變成了Object get();List就變成了List。

  泛型的實現原理

  因為種種原因,Java不能實現真正的泛型,只能使用類型擦除來實現偽泛型,這樣雖然不會有類型膨脹(C++模板令人困擾的難題)的問題,但是也引起了許多新的問題。所以,Sun對這些問題作出了許多限制,避免我們犯各種錯誤。

  保證類型安全

  首先第一個是泛型所宣稱的類型安全,既然類型擦除了,如何保證我們只能使用泛型變量限定的類型呢?java編譯器是通過先檢查代碼中泛型的類型,然后再進行類型擦除,在進行編譯的。那類型檢查是針對誰的呢,讓我們先看一個例子。

  ArrayList arrayList1=new ArrayList(); // 正確,只能放入String

  ArrayList arrayList2=new ArrayList(); // 可以放入任意Object

  這樣是沒有錯誤的,不過會有個編譯時警告。不過在第一種情況,可以實現與 完全使用泛型參數一樣的效果,第二種則完全沒效果。因為,本來類型檢查就是編譯時完成的。new ArrayList()只是在內存中開辟一個存儲空間,可以存儲任何的類型對象。而真正涉及類型檢查的是它的引用,因為我們是使用它引用arrayList1 來調用它的方法,比如說調用add()方法。所以arrayList1引用能完成泛型類型的檢查。 而引用arrayList2沒有使用泛型,所以不行。

  類型檢查就是針對引用的,誰是一個引用,用這個引用調用泛型方法,就會對這個引用調用的方法進行類型檢測,而無關它真正引用的對象。

  實現自動類型轉換

  因為類型擦除的問題,所以所有的泛型類型變量最后都會被替換為原始類型。這樣就引起了一個問題,既然都被替換為原始類型,那么為什么我們在獲取的時候,不需要進行強制類型轉換呢?

  public class Test {

  public static void main(String[] args) {

  ArrayList list=new ArrayList();

  list.add(new Date());

  Date myDate=list.get(0);

  }

  }

  編譯器生成的class文件中會在你調用泛型方法完成之后返回調用點之前加上類型轉換的操作,比如上文的get函數,就是在get方法完成后,jump回原本的賦值操作的指令位置之前加入了強制轉換,轉換的類型由編譯器推導。

  泛型中的繼承關系

  先看一個例子:

  class DateInter extends A {

  @Override

  public void setValue(Date value) {

  super.setValue(value);

  }

  @Override

  public Date getValue() {

  return super.getValue();

  }

  }

  先來分析setValue方法,父類的類型是Object,而子類的類型是Date,參數類型不一樣,這如果實在普通的繼承關系中,根本就不會是重寫,而是重載。

  public void setValue(java.util.Date); //我們重寫的setValue方法

  Code:

  0: aload_0

  1: aload_1

  2: invokespecial #16 // invoke A setValue

  :(Ljava/lang/Object;)V

  5: return

  public java.util.Date getValue(); //我們重寫的getValue方法

  Code:

  0: aload_0

  1: invokespecial #23 // A.getValue

  :()Ljava/lang/Object;

  4: checkcast #26

  7: areturn

  public java.lang.Object getValue(); //編譯時由編譯器生成的方法

  Code:

  0: aload_0

  1: invokevirtual #28 // Method getValue:() 去調用我們重寫的getValue方法

  ;

  4: areturn

  public void setValue(java.lang.Object); //編譯時由編譯器生成的方法

  Code:

  0: aload_0

  1: aload_1

  2: checkcast #26

  5: invokevirtual #30 // Method setValue; 去調用我們重寫的setValue方法

  )V

  8: return

  并且,還有一點也許會有疑問,子類中的方法 Object getValue()和Date getValue()是同 時存在的,可是如果是常規的兩個方法,他們的方法簽名是一樣的,也就是說虛擬機根本不能分別這兩個方法。如果是我們自己編寫Java代碼,這樣的代碼是無法通過編譯器的檢查的,但是虛擬機卻是允許這樣做的,因為虛擬機通過參數類型和返回類型來確定一個方法,所以編譯器為了實現泛型的多態允許自己做這個看起來“不合法”的事情,然后交給虛擬器去區別。

  我們再看一個經常出現的例子。

  class A {

  Object get(){

  return new Object();

  }

  }

  class B extends A {

  @Override

  Integer get() {

  return new Integer(1);

  }

  }

  public static void main(String[] args){

  A a = new B();

  B b = (B) a;

  A c = new A();

  a.get();

  b.get();

  c.get();

  }

  反編譯之后的結果

  17: invokespecial #5 // Method com/suemi/network/test/A.””:()V

  20: astore_3

  21: aload_1

  22: invokevirtual #6 // Method com/suemi/network/test/A.get:()Ljava/lang/Object;

  25: pop

  26: aload_2

  27: invokevirtual #7 // Method com/suemi/network/test/B.get:()Ljava/lang/Integer;

  30: pop

  31: aload_3

  32: invokevirtual #6 // Method com/suemi/network/test/A.get:()Ljava/lang/Object;

  實際上當我們使用父類引用調用子類的get時,先調用的是JVM生成的那個覆蓋方法,在橋接方法再調用自己寫的方法實現。

  泛型參數的繼承關系

  在Java中,大家比較熟悉的是通過繼承機制而產生的類型體系結構。比如String繼承自Object。根據Liskov替換原則,子類是可以替換父類的。當需要Object類的引用的時候,如果傳入一個String對象是沒有任何問題的。但是反過來的話,即用父類的引用替換子類引用的時候,就需要進行強制類型轉換。編譯器并不能保證運行時刻這種轉換一定是合法的。這種自動的子類替換父類的類型轉換機制,對于數組也是適用的。 String[]可以替換Object[]。但是泛型的引入,對于這個類型系統產生了一定的影響。正如前面提到的List是不能替換掉List的。

  引入泛型之后的類型系統增加了兩個維度:一個是類型參數自身的繼承體系結構,另外一個是泛型類或接口自身的繼承體系結構。第一個指的是對于 List和List這樣的情況,類型參數String是繼承自Object的。而第二種指的是 List接口繼承自Collection接口。對于這個類型系統,有如下的一些規則:

  相同類型參數的泛型類的關系取決于泛型類自身的繼承體系結構。即List可以賦給Collection 類型的引用,List可以替換Collection。這種情況也適用于帶有上下界的類型聲明。 當泛型類的類型聲明中使用了通配符的時候, 這種替換的判斷可以在兩個維度上分別展開。如對Collection

非常好我支持^.^

(0) 0%

不好我反對

(0) 0%

用戶評論

      ?
      主站蜘蛛池模板: 亚洲性无码AV久久成人 | 久久成人精品免费播放 | 国产毛片视频网站 | 欧美高清xxx | 免费国产午夜理论不卡 | 免费看成人毛片 | 国产欧美一区二区三区视频 | 卫生间被教官做好爽HH视频 | free18sex性自拍裸舞 | 八妻子秋霞理在线播放 | 国产无遮挡色视频免费观看性色 | 近亲乱中文字幕 | 亚洲spank男男实践网站 | 国产成人免费在线 | 中文字幕在线免费视频 | 一个人在线观看免费中文www | 少妇伦子伦情品无吗 | 香蕉水蜜桃牛奶涩涩 | 日本理伦片午夜理伦片 | 欧美性猛交xxxxxxxx软件 | 国产高清砖码区 | 国内免费视频成人精品 | 久久视频这有精品63在线国产 | 亚洲国产精品久久精品成人网站 | 精品免费久久久久久影院 | 免费a毛片 | 动漫美女的阴 | 日本人娇小hd | 肉多的小说腐小说 | 好想被狂躁A片免费久99 | 我的美女奴隶 | 黄色片软件大全 | 色鲁97精品国产亚洲AV高 | 嫩草AV久久伊人妇女 | 中文字幕在线不卡日本v二区 | 中国午夜伦理片 | 日韩精品免费在线观看 | 里番acg纲手的熟蜜姬训练场 | 在线观看qvod | 婷婷五月久久精品国产亚洲 | 三级全黄a|