public class Holder3<T> {
private T a;
public Holder3(T a ){
this.a=a;
}
public void set(T a){this.a=a;}
public T get(){return a;}
public static void main(String[] args){
Holder3<Animal> h3=new Holder3<Animal>(new AnimalWrapper());
}
}
就象main方法,
當(dāng)創(chuàng)建Holder3對象時,必須指明向持有什么類型的對象,將其置于尖括號內(nèi)。
定義泛型方法,只需要將泛型參數(shù)列表置于返回值之前,就像下面:
public class GenericMethods {
public <T> void f(T x){
System.out.println(x.getClass().getName());
}
public static void main(String[] agrs){
GenericMethods gm=new GenericMethods();
gm.f("");
gm.f(1);
gm.f(1.0);
gm.f(1.0F);
gm.f(gm);
}
}
結(jié)果:
java.lang.String
java.lang.Integer
java.lang.Double
java.lang.Float
proxy.generic.GenericMethods
如果是泛型類,必須在創(chuàng)建對象的時候指定類型參數(shù)的值,而是用泛型方法的時候,通常不必指明參數(shù)類型,因為編譯器會為我們找出具體的類型,這稱為類型參數(shù)推斷。因此,可以想調(diào)用普通方法一樣調(diào)用f(),而且,就好像是f()被無限次重載過。
3.1 初步擦除
Class c1=new ArrayList<String>().getClass();
Class c2=new ArrayList<Integer>().getClass();
System.out.println(c1==c2);
從代碼上看,我們很容易認為是不同的類型。不同的類型在行為上不同,例如嘗試將一個Integer放入ArrayList<String>,所得到的行為(失?。┡cInteger放入ArrayList<Integer>,所得到的行為完全不同,但是程序會打印出來相同。下面是另外一個奇怪的程序:
public class LostInformation {
public static void main(String[] args){
List<Frob> list=new ArrayList<Frob>();
Map<Frob,Fnorkie> map=new HashMap<Frob, Fnorkie>();
Quark<Fnorkie> quark=new Quark<Fnorkie>();
Particle<Long,Double> p=new Particle<Long, Double>();
System.out.println(Arrays.toString(list.getClass().getTypeParameters()));
System.out.println(Arrays.toString(map.getClass().getTypeParameters()));
System.out.println(Arrays.toString(quark.getClass().getTypeParameters()));
System.out.println(Arrays.toString(p.getClass().getTypeParameters()));
}
}
class Frob{}
class Fnorkie{}
class Quark<Q>{}
class Particle<POSITION,MOMENTUN>{}
[E]
[K, V]
[Q]
[POSITION, MOMENTUN]
Class.getTypeParameters()的說明看起來是返回一個TypeVarible對象數(shù)組,表示有泛型聲明所聲明的類型參數(shù),這好像是暗示你可能發(fā)現(xiàn)參數(shù)類型的信息,但是,正如結(jié)果看到的,你能夠發(fā)現(xiàn)的只是用作參數(shù)占位符的標(biāo)識符,殘酷的現(xiàn)實是:
在泛型代碼內(nèi)部,無法獲得任何有關(guān)泛型參數(shù)類型的信息。
Template<class T> class Manipulator{
T obj;
public:
Void manipulator(T x){obj.f();}
}
在C++的實現(xiàn)中,obj上可以調(diào)用f()方法,為什么?當(dāng)實例化這個模板時,C++編譯器將進行檢查,因此在Manipulator<HasF>被實例化的這一刻,看到HasF擁有一個方法f(),如果不是這樣,將會得到一個編譯期錯誤,這樣類型安全就得到了保障。
在Java中不能這么做,除非借助泛型類的邊界,以此告訴編譯器,只能接受遵循這個邊界的類型,這里重用了extends關(guān)鍵字:
Class Mainpulator<T extends Hasf>{
Private T obj;
Public void mainpulate{obj.f();}
}
擦除并不是語言的一個特性,是Java實現(xiàn)泛型的一種折中,因為泛型不是Java語言出現(xiàn)時就有的組成部分,所以這種折中是必須的。
如果泛型在Java1.0中就已經(jīng)是其一部分,那么這個特性就不會使用搽除來實現(xiàn),它將具體化,使類型參數(shù)保持為第一類試題,因此你就能夠在泛型參數(shù)上執(zhí)行基于類型的語言操作和反射操作。主要是為了向后兼容性,即現(xiàn)有的代碼和類文件仍舊合法,并且繼續(xù)保持其之前的含義,而且還要支持遷移兼容性,使得類庫按照它們自己的步調(diào)變?yōu)榉盒偷?/b>。
在基于擦除的實現(xiàn)中,泛型類型被當(dāng)作第二類類型處理,即不能再某些重要的上下文環(huán)境中使用的類型,泛型類型只有在靜態(tài)類型檢查期間出現(xiàn),在此之后,程序中所有泛型類型都將被擦除,替換為它們的非泛型上界,例如,諸如List<T>被擦除為List,而普通的類型變量在未指定邊界的情況下,被擦除為Object。
public class ArrayMaker<T> {
private Class<T> kind;
public ArrayMaker(Class<T> kind){this.kind=kind;}
T[] create(int size){
//這里必須轉(zhuǎn)型成有意義的類型,因為T并沒有包含具體的意思,它被擦除了,會 //有警告
return (T[])Array.newInstance(kind, size);
}
public static void main(String[] grs){
ArrayMaker<String> stringMarker=
new ArrayMaker<String>(String.class);
String[] stringArray=stringMarker.create(9);
System.out.println(Arrays.toString(stringArray));
}
}
結(jié)果:
[null, null, null, null, null, null, null, null, null]
即使kind被存儲為Class<T>,擦除也意味著它實際被存儲為Class,沒有任何參數(shù)。因此,當(dāng)你在使用它時,例如創(chuàng)建數(shù)組時,Array.newInstance()實際上并未擁有kind所蘊含的類型信息,因此不會產(chǎn)生具體的結(jié)果。在Java中推薦使用工廠(直接使用class.newInstance()或者顯示的工程方法)方法或者模板方法來解決這類創(chuàng)建問題
3.5 擦除的邊界
雖然在運行的時候,擦除在方法體重移除了類型信息,但是在邊界(看了下面的例子再解釋邊界)的時候,還是會進行類型轉(zhuǎn)換(檢查)。
public class FilledListMaker<T> {
List<T> create(T t,int n){
List<T> result=new ArrayList<T>();
for(int i=0;i<n;i++){
result.add(t);
}
result.add((T)new b());
return result;
}
public static void main(String[] agrs){
FilledListMaker<demo> stringMarker=
new FilledListMaker<demo>();
List<demo> list=stringMarker.create(demo.init("44"), 4);
System.out.println(list.get(4));
//這里發(fā)生了異常
System.out.println(list.get(4).getaa());
System.out.println(list);
}
}
上面的泛型接受demo類型,但是我們悄悄放入了一個b類型。
用javap -c FilledListMaker反編譯類,會得到下面的內(nèi)容:
所以可以記住,邊界就是發(fā)生在需要轉(zhuǎn)型的地方。
創(chuàng)建一個new T()的嘗試將無法實現(xiàn),部分原因是因為擦除,另一部分原因是因為編譯器不能驗證具有默認的無參構(gòu)造函數(shù),但是在C++中,這種操作很自然,很直觀,并且很安全。
Java中的解決方案是傳遞一個工廠對象,并使用它來創(chuàng)建新的實例,最便利的工廠就是Class對象,因此,如果使用類型標(biāo)簽,就可以使用newInstance來創(chuàng)建這個類型的新對象:
public class ClassAsFactory<T> {
T x;
public ClassAsFactory(Class<T> kind){
try {
x=kind.newInstance();
} catch (Exception e){
throw new RuntimeException(e);
}
}
public static void main(String[] args){
ClassAsFactory<Employee> fe=new
ClassAsFactory<Employee>(Employee.class);
//這里會拋出異常,因為Integer沒有默認的構(gòu)造函數(shù)
ClassAsFactory<Integer> f1=new
ClassAsFactory<Integer>(Integer.class);
}
}
class Employee{}
可以看到,這種方式,在某些情況下會有異常,因為Integer沒有任何默認的構(gòu)造函數(shù),所以sun并不是很推薦使用這種方式,建議使用顯示的工廠,并將限制其類型,使得智能接受實現(xiàn)了這個工廠的類:
1.上面提到了邊界,因此擦除移除了類型信息,所以,可以用無界泛型參數(shù)調(diào)用的方法只是那些可以用Object調(diào)用的方法,但是,如果能夠?qū)⑦@個參數(shù)限制為某個類型子集,那么就可以用這些類型子集來調(diào)用方法。為了執(zhí)行這種限制,Java泛型重用了extends關(guān)鍵字,
interface face1 {}
interface face2 {}
class class1 {}
class Bound<T extends class1 & face1 & face2> {}
在子類還能加入更多的限定
interface face3 {}
class BoundDerived<T extends class1 & face1 & face2 & face3> extends Bound<T> {}
2.Super關(guān)鍵字限定了下界,但是沒有限定上界,所以
ArrayList<? super Derived1> alsb = new ArrayList<Base1>();
alsb.add(new Derived1()); //success
// alsb.add(new Base1()); // error: The method add(capture#4-of ? super Derived1) in the type ArrayList<capture#4-of ? super Derived1> is not applicable for the arguments (Base1)
Object d = alsb.get(0); // return an Object
可以看到在接受參數(shù)時限制放寬了,因為編譯器知道范型的下界,只要是Derived類或它的子類都是合法的。但是在返回時,它只能返回Object類型,因為它不能確定它的上界。
3.無界通配符,即<?>,與原生類型(非范型類)大體相似
個人感覺這部分是在太繞了,看java編程思想里面那么多的解釋,還是大概知道咋用就好了,貼一個我之前系統(tǒng)的設(shè)計圖吧
4 關(guān)于取得泛型的類型
4.1 Type體系
在java5之后,java加入了type體系,這部分太麻煩了,以后有機會用到再寫吧,shit
Java泛型有這么一種規(guī)律:
位于聲明一側(cè)的,源碼里寫了什么到運行時就能看到什么;
位于使用一側(cè)的,源碼里寫什么到運行時都沒了。
聲明一側(cè),即在Class類內(nèi)的信息;使用一側(cè),即在一些方法體內(nèi)部
public class GenericClassTest<A,B extends Number> {
private List<String> list;
public static void main(String[] args) throws NoSuchFieldException, SecurityException{
GenericClassTest<String,Integer> gc=new GenericClassTest<String,Integer>();
for(TypeVariable ty:gc.getClass().getTypeParameters()){
System.out.println(ty.getName());
}
out(((ParameterizedType)gc.getClass().getDeclaredField("list")
.getGenericType()).getOwnerType());
out(((ParameterizedType)gc.getClass().getDeclaredField("list")
.getGenericType()).getRawType());
out(((ParameterizedType)gc.getClass().getDeclaredField("list").
getGenericType()).getActualTypeArguments()[0]);
}}
輸出:
A
B
null
interface java.util.List
class java.lang.String