標籤:問題 icc 強制轉換 編程 無限 接收 void 虛擬機器 throw
泛型引入泛型傳統編寫的限制:
在Java中一般的類和方法,只能使用具體的類型,要麼是基礎資料型別 (Elementary Data Type),要麼是自訂類型。如果要編寫可以應用於多種類型的代碼,這種刻板的限制就會束縛很多!
解決這種限制的三種方法:
1.多態:將方法的參數類型設為基類,那麼該方法就可以接收從這個基類匯出的任何類作為參數。
class Primary{} //定義基類class Test(){ public void f(Primary p) {...}}
2.方法的參數使用介面:任何實現了該介面的類都可以滿足該方法。
interface Primary{} //定義介面class Test(){ public void f(Primary p) //實現了該介面的所有類都可以作為參數 {...}}
3.使用泛型。
泛型的說明:
泛型實現了參數化型別的概念,使代碼可以應用於某種不具體的類,而不是具體的一個類或者介面。
簡單說就是使代碼可以適用於廣泛的類型。
泛型運算式的翻譯(最後看):
1.當程式調用泛型方法時,如果擦除了泛型傳回型別,編譯器插入類型轉換。
Pair<Employee> buddies = ... Employee buddy = buddies.getFirst();
? 擦除getFirst的傳回型別後將返回Object類型,編譯器自動插入Employee的強制類型轉換。
? 也就是說,編譯其把這個方法調用翻譯為兩條虛擬指令:
◇ 對原始方法Pair.getFirest的調用。
◇ 將返回的Object類型,強制轉換為Employee類型。
2.當存取一個泛型域時也要插入強制類型轉換。
假設 Pair 類的first 域 和 second 域都是 公有的(這不是種好的編程風格, 但在java文法中,這是合法的)。
運算式: Employee buddy = buddies.first; 也會在結果位元組碼中插入強制類型轉換;
簡單泛型:
使用泛型預定義參數類型:
說明:基礎資料型別 (Elementary Data Type)無法作為型別參數
在使用時具體化型別參數
小結
泛型類最主要的使用是應用在集合中,代碼封裝了一個ArrayList,這樣我們在編寫類的時候,就不會受限於具體的類,因為具體使用的類型是在初始化對象的時候才指定的。
我們為什麼要這樣呢?如果這個實作類別不用泛型,如果處理多種類型資料的時候,就要編寫多個實作類別,來針對處理。
元組:簡單元組執行個體:
通過繼承機制來實現長度更長的元組
泛型介面說明:
泛型也可以應用於介面,類和介面的型別參數應該保持一致,都是T或者其他。
泛型介面最常用的一個用法是實現Iterable介面,實現迭代方法!
執行個體:
import java.util.ArrayList;import java.util.Date;import java.util.Iterator;import java.util.Random;public class RandomList<T> implements Iterable<T> { private ArrayList<T> storage = new ArrayList<T>(); private Random rand = new Random(new Date().getTime()); public void add(T item) { storage.add(item); } public T select() { return storage.get(rand.nextInt(storage.size())); } /* 實現Iterable介面 */ public Iterator<T> iterator() { return storage.iterator(); }}
泛型方法說明:
是否擁有泛型方法和其是否是泛型類並無直接關係,也就是說泛型方法可以獨立於類而產生變化!
執行個體:
類型說明:顯式的類型說明:
在泛型方法中,可以顯式地指明參數類型,不過很少這樣使用!
可變參數列表:
泛型方法和可變參數列表可以很好的共存
類型變數的限定說明:
1.添加限定來讓類型變數具有特定功能。
2.可以有多個限定(介面和類都是可以的),但要注意順序。
為什麼關鍵字不用implements呢?畢竟Comparable是一個介面:
<T extends BoundingType>
這句代碼錶示T應該是綁定類型(BoundingType)的子類型。T和綁定類型可以是類,也可以是介面。
若T是介面,那麼方法調用時傳進來的是T的實作類別也是可以的。extends更接近於子類的概念,所以選用extends。
神秘的擦除
問題:
我們很可能會認為c1!=c2,結果為false,因為c1不能裝入Integer,c2也不能裝入String。
說明:
在泛型代碼內部,無法擷取任何有關泛型參數類型的資訊,也就是你無法獲得那個具體的類型是什麼,你僅僅可以獲知的諸如型別參數標識符和泛型邊界這類的資訊。
Java泛型是使用擦除來實現的:
這意味者當你在使用泛型的時候,任何具體的類型資訊都被擦除了,你唯一知道的就是你在使用一個對象。因此問題中c1==c2,為true。這兩種形式都被擦除成為它們的“原生”類型,即List.
原始類型與擦除:
虛擬機器沒有泛型型別對象,所有對象都屬於普通類。無論何時定義了一個泛型型別,都自動提供了一個相應的原始類型,將泛型型別還原成原始類型的過程,稱為擦除。
原始類型的名字就是刪去型別參數後的泛型型別名。擦除類型變數,並替換為限定類型(無限定類型的變數預設用Object)。
左圖的原始類型變為:public class Test<Object>,所有T都變成Object 右圖變為:public class Test<Comparable>,所有T都變成Object。
說明:
這樣就不難理解,為什麼,加上限定邊界後,我們就可以調用obj.compareTo()了,因為擦除類型變數後,T都被還原成Comparable,效果就是任何實現了Comparable的子類都可以調用Test對象的compare()方法。如果泛型是在Java 1.0就出現的,那麼這個特性將不會使用擦除來實現——它將使用具體化,使型別參數保持為第一類的實體,因此你就能夠在型別參數上執行基於類型的語言操作和反射操作。
再一次強調:
Test<Cat> test = new Test<Cat>();
看起來class Test應該知道現在工作於Cat之上,而泛型文法也在強烈暗示,在整個類中的各個地方,類型T都在被替換。但是事實並非如此,無論何時,當你在編寫這個類的代碼時,必須時刻提醒自己:它僅僅只是一個Object。
擦除的補償。
再談邊界
1.邊界使得你可以在用於泛型的型別參數上設定限制條件。儘管這使得你可以強制規定泛型可以應用的類型,但是其潛在的一個更重要的效果是你可以按照自己的邊界類型來調用方法。
2.因為擦除移除了類型資訊,所以,可以用無界泛型參數調用的方法只能是那些可以用Object調用的方法,那麼你就可以用這些類型位元組來調用方法。
萬用字元
說明:
1.Java泛型是強制類型檢測的,泛型型別的子類型互不相關。
publicclass Test { public static void main(String[] args) throws Exception{ List<Integer> listInteger =new ArrayList<Integer>(); List<String> listString =new ArrayList<String>(); printCollection(listInteger); printCollection(listString); } public static void printCollection(Collection<Object> collection){ for(Object obj:collection){ System.out.println(obj); } } } //Integer String都是Object的子類型,但是結果會報錯,這就說明了泛型不考慮繼承關係The method printCollection(Collection<Object>) in the type GernericTest is not applicable for the arguments (List<Integer>)
2.我們希望泛型能向普通類那樣具有物件導向的一些特徵:
-
- 向上轉型為一個泛型對象。
- 向下轉型為一個泛型對象。
3.為了使泛型能具有物件導向的一些繼承關係,Java引入了萬用字元的一些概念:無界萬用字元 ?
為了使泛型的子類型仍然具有相關性,可以直接使用無界萬用字元:
使用萬用字元上界“? extends T”,來指定繼承關係
說明:
? extends T 的本質上的實現是泛型的自動向上轉型。
使用萬用字元下界“?super T”
使用方法和解釋同上。
Java:泛型基礎