學習集合框架相關內容之前還是要把泛型好好看下,要不各種源代碼看得就很難受了,一遇到<? ><T> 這樣的一些表述就頭大了,這部分可結合着集合的相關內容一起了解。
泛型基本概念(Genetics)
就像聖思園視頻里講的,用一句比較好的話解釋就是:變量類型的參數化。泛型基本思想與C++的模板中的思想比較類似,但是在還有一些區別的比如具體的實現方式上。
使用集合的時候比如按照下面的沒有用泛型的方式(其實是<? Extends Object>):
List list=new ArrayList();
List.add(“abc”);list.add(new Integer(1));list.add(new Boolean(true))
String str=(String)list.get(0);Integer a=(Integer)list.get(1);String str2=(String)list.get(2)
上面的例子在編譯的時候不會報錯,在運行的時候會報錯,因為index=2位置上的元素是一個Boolean類型的,這里卻要強制轉化成一個String類型的。
這就是之前的弊端,因為所有的類全是Object的類型的子類,因此全當做Object類型來處理,但是在取出的時候恢復到原來的類型就遇到了問題,需要進行強制的轉換,這樣操作雖然可以但是不能保證安全性,就像上面的例子那樣。
可以說泛型的一個重要作用就是為了避免強制類型的轉換,定義的時候邏輯與之前的相同,涉及到具體的類型信息的時候,用一個符號來代替(所謂類型的參數化),不變應萬變。
比如下面一個簡單的泛型例子:
package com.test.Genetics; class Genetics<T>{ //這里的T 可以看做是表示類型的參數 //代碼中遇到類型的部分 就用T來替代就好 //T看做是一個變量 具體傳入的類型只有在運行的時候才能知道 public T foo; public T getFoo() { return foo; } public void setFoo(T foo) { this.foo = foo; } } public class testGenetics <T> { public static void main(String[] args) { Genetics<Boolean> foo1=new Genetics<Boolean>(); Genetics<Integer> foo2=new Genetics<Integer>(); foo1.add("abc"); } }
可以看到上面的例子,在實際生成類的實例的時候要預先把類型信息填入其中,這樣要是add了其他的類型的實例進去就會報錯了,foo1.add(“abc”)這句在編譯的時候就會報錯,而不會等到運行的時候。實際的集合框架都是通過泛型來實現的,這樣在聲明的時候就要指定好類型信息,在取出的時候也不用再進行強制的類型轉換了。
這個是傳入兩個泛型參數的例子:
package com.test.Genetics; //這個類里面有兩個通過泛型表示的類型變量 public class testGeneticb <T1,T2> { private T1 foo1; private T2 foo2; public T1 getFoo1() { return foo1; } public void setFoo1(T1 foo1) { this.foo1 = foo1; } public T2 getFoo2() { return foo2; } public void setFoo2(T2 foo2) { this.foo2 = foo2; } public static void main(String[] args) { testGeneticb<Integer,Boolean> foo=new testGeneticb<Integer,Boolean>(); foo.setFoo1(10); foo.setFoo2(new Boolean(false)); System.out.println("foo1 is "+foo.getFoo1() +" foo2 is " + foo.getFoo2()); } }
下面這個是用泛型實現的一個簡單集合,功能比較簡單,主要是為了演示對於泛型的操作,注意聲明泛型類的構造方法的時候不用加上<T>:
package com.test.Genetics; public class simpleCollection <T>{ private T[] objArr; private int index=0; public T[] getObjArr() { return objArr; } public void setObjArr(T[] objArr) { this.objArr = objArr; } public int getIndex() { return index; } public void setIndex(int index) { this.index = index; } //默認參數的時候 數組空間大小為10 public simpleCollection(){ this.objArr=(T[])new Object[10]; } //泛型類的構造方法不用加<>標記了 public simpleCollection(int capacity) { //注意無法直接創建泛型數組 要先創建Object類型的數組之后再強制轉換過來 this.objArr=(T[])new Object[capacity]; } //這里形參是一個T類型的引用 t public void add(T t){ this.objArr[index++]=t; } public int getLength(){ return this.index; } public T get(int i){ return this.objArr[i]; } public static void main(String[] args) { int i; simpleCollection<Integer> c=new simpleCollection<Integer>(); //存入元素再打印出來 for(i=0;i<5;i++){ c.add(i); } System.out.println("the length is "+c.getLength()); for(i=0;i<5;i++){ Integer in=c.get(i); System.out.println(in); } } }
下面這個例子用於演示泛型的嵌套
package com.test.Genetics; class tmpGenetics<T>{ private T foo; public T getFoo() { return foo; } public void setFoo(T foo) { this.foo = foo; } } public class wrapperFoo <T>{ private tmpGenetics<T> tmpfoo; public tmpGenetics<T> getTmpfoo() { return tmpfoo; } public void setTmpfoo(tmpGenetics<T> tmpfoo) { this.tmpfoo = tmpfoo; } public static void main(String[] args) { tmpGenetics<Integer>foo=new tmpGenetics<Integer>(); foo.setFoo(10); wrapperFoo<Integer>wrapper=new wrapperFoo<Integer>(); wrapper.setTmpfoo(foo); //將之前存進去的foo對象拿出來 賦給一個新的tmpGenetics<Integer> tmpGenetics<Integer>outfoo=wrapper.getTmpfoo(); System.out.println(outfoo.getFoo()); } }
下面這個主要演示Map框架中泛型的基本使用以及迭代器的時候泛型的使用(這個可以結合集合框架的那一部分對於Map中的entry的介紹具體來看)
package com.test.Genetics; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Set; public class testMap { public static void main(String[] args) { Map<String,String> map=new HashMap<String,String>(); map.put("a", "aa"); map.put("b", "bb"); map.put("c", "cc"); map.put("d", "dd"); System.out.println("using first kind iterator way"); Set<String>keyset=map.keySet(); for(Iterator<String>iter=keyset.iterator();iter.hasNext();){ String key=iter.next(); String value=map.get(key); System.out.println("the key is "+key+"the value is "+value); } //采用第二種方式迭代輸出 //注意這里的set聲明的寫法 entry也是一個泛型類(是Map集合中的靜態內部類) 其中包含了key value System.out.println("using second kind iterator way"); Set<Entry<String, String>>entryset=map.entrySet(); for(Iterator<Map.Entry<String, String>>iter=entryset.iterator();iter.hasNext();){ Map.Entry<String, String>entry=iter.next(); String key=entry.getKey(); String value=entry.getValue(); System.out.println("the key is "+key+"the value is "+value); } } }
關於限制泛型的作用類型
在實際的開發中要是對泛型的具體實現有限制怎么處理?
比如像下面這種類型聲明:
public class Genetics<T extends someclass>這種是固定的語法,其中somclass這個地方表示希望限制的類的名字,即使泛型類在實際聲明的時候,<>中只能是這個類及其子類的類型名稱。比如<T extends List> 那么生成實例的時候HashMap就不能往<>中放了。
實際上直接聲明的<T>相當於<T extends Object>因此在默認的情況下,任何類型都可以作為類型參數,在聲明一個泛型類的實例的時候放在<>中。
還有一種情況比如希望聲明一個泛型類的引用foo可以實現以下方式:
foo = new Genetics<ArrayList>;
foo = new Genetics<LinkedList>;
即是說foo這個引用不是那種一一對應的關系,(就是說我在用泛型的方式聲明一個引用的時候不希望這個引用與具體的某個類型的實例互相綁定)聲明一個泛型類的引用之后可以將這個引用指向多個泛型類的實例,這就需要用到通配符的方式即
public class Genetics<? extends List> { …}
這樣生成引用 Genetics<? extends List> foo之后,foo可以指向類型參數為List或其子類的不同類型的實例。還可以通過<? super List> 這種方式聲明表示繼承結構在List上面的,實際中用到的比較少。
注意兩種限制方式的區別:
第一種在聲明泛型引用的時候就定死了,是一一對應的關系,某種特定類型的引用只能指向對應的特定類型的泛型實例。
第二種是聲明引用的時候沒有定義好,或者是局部限定范圍,是一對多的關系,某種引用可指向限制范圍內的多個特定類型的泛型實例。第二種方式僅僅是在聲明一個引用的時候才用到的,在聲明泛型類的時候還是用之前的方式。
比如我聲明一個泛型類
Class classA <T extends List>{}
之后我在main函數中生成這個類的引用的實例
classA<List> l=new classA<List>();(注意聲明引用的時候寫成classA<T extends List> l 這樣是報錯的 <>中只能寫成實際的類型或者用通配符的那種表示方式)
l=new classA<LinkedList>();
這樣顯然就報錯了。
如果我改成 classA<? extends List> l=new classA<List>(); l=new classA<LinkedList>();這樣編譯器就不會報錯了。
還要注意一下,只是聲明引用的時候才能用通配符的方式,聲明一個泛型類的時候還要用原來的那種有參數的形式。
使用通配符的方式要注意的一點是,這種方式意味着只能通過這個引用來取得或者是移除(設為null)實例中的某些信息,但不能增加修改信息,因為只知道這個引用指向的是somclass的子類,但是具體指向的是哪種類型在編譯期間並不知道,因此編譯器不能讓修改操作發生,若是可以發生,就是在編譯之前已經知道了具體指向的是哪個類,這個就和泛型的初衷相違背了,比如下面這個例子:
package com.test.Genetics; class tmpGeneticsb<T>{ private T str; public T getStr() { return str; } public void setStr(T str) { this.str = str; } } public class testExtends { public static void main(String[] args) { tmpGeneticsb<String>ge=new tmpGeneticsb<String>(); ge.setStr("abc"); //注意采用通配符的方式 僅僅是在聲明指向泛型類的引用的時候才用到的 //聲明一個泛型類的時候並不能這樣使用 //<? extends Object>表示了這個引用ge2可以指向所有類型的泛型實例 tmpGeneticsb<? extends Object>ge2; ge2=ge; //下面的操作就會無法通過編譯 //ge2.setStr("cde"); } }
關於泛型方法:
之前還是忽略了關於泛型方法,做作業時候用到了,再補充一下,泛型方法不一定要定義在泛型類中,也可以是定義在普通的類中,類型變量要放在修飾符的后面,返回類型的前面。比如最基本的方式,像這樣的聲明:
public static <T> T getMiddle(T[]a){
return a[a.length/2]
}
其中<T>表示類型的變量,或者說是限制,這里一個<T>可以看成是<T extends Object>當然也可以有其他的限制,就像上面介紹的那樣,比如要對繼承變量加以限制,就繼承對應的類,比如要在方法中實現比較的操作,就寫成<T extends Comparable>(注意這里使用extends而不是implements這個是固定的語法),這樣就限制了T應該是comparable的子類型,當然一個類型變量的限定可以有多個,比如<T extends Comparable&Serializable>
還要再補充一些
並不是只有集合的相關框架使用到了泛型,事實上java.lang.Class也是通過泛型來進行定義的,只不過平時用的時候不太在意,Class<T>這里面的這個泛型參數就表示Class對象所修飾的那個泛型類的類型,應該更多的還是出於對安全性的考慮。
比如Class<String> classtype=String.class;之后 classtype=Integer.class;這樣的話編譯器就肯定是報錯了。如果不使用泛型的話,雖然不會報錯,但是可能不太安全,比如Class classtype=String.class;classtype=Integer.class;這樣編譯器只會發出警告但是不會報錯,但顯然是不安全的使用方式。