java泛型應(yīng)用是java核心基礎(chǔ)之一,,從java 5開始引進(jìn)泛型。如果你曾經(jīng)使用過java Collection,,那你已經(jīng)算是接觸過泛型了,。在java Collection里使用泛型是一件很簡單的事情,可泛型還具有很多你意想不到的作用,。在深入了解泛型之前,,首先來了解一下泛型的一些基本概念與原理。 一,、java 泛型引入java泛型的應(yīng)用可以提高的代碼的復(fù)用性,,同時(shí)泛型提供了類型檢查,減少了數(shù)據(jù)的類型轉(zhuǎn)換,,同時(shí)保證了類型安全,。下面看一下,泛型如何保證了類型安全: List list = new ArrayList(); list.add("abc"); list.add(new Integer(1)); //可以通過編譯 for (Object object : list) { System.out.println((String)object);//拋出ClassCastException異常 }
List<String> list = new ArrayList<>(); list.add("abc"); //list.add(new Integer(1)); //編譯錯(cuò)誤 for (String string : list) { System.out.println(string);//無需任何強(qiáng)制類型轉(zhuǎn)換 }
二、泛型的類與接口既然是學(xué)泛型,,自然就要知道如何去使用泛型定義自己的類和接口,。同時(shí)為了加深理解泛型的作用,先引進(jìn)一個(gè)原始的類: public class Gen { private Object obj; public Object getObj() { return obj; } public void setObj(Object obj) { this.obj = obj; } public static void main(String[] args) { Gen gen = new Gen(); gen.setObj("abc"); String str = (String) gen.getObj();//類型轉(zhuǎn)換,,可能會(huì)引起運(yùn)行時(shí)ClassCastException } }
public class Gen<T> { T obj; public T getObj() { return obj; } public void setObj(T obj) { this.obj = obj; } public static void main(String[] args) { Gen<String> gen = new Gen<>(); gen.setObj("abc"); // gen.setObj(10); //無法通過編譯 String str = gen.getObj(); //無需類型轉(zhuǎn)換 //----------------------------- Gen gen2 = new Gen();//raw type原始類型 gen2.setObj("abc"); gen2.setObj(10); //可以通過編譯,自動(dòng)裝箱將10轉(zhuǎn)化為Integer對(duì)象 Integer num = (Integer) gen2.getObj();//使用了強(qiáng)制類型轉(zhuǎn)換 } } 細(xì)心的你會(huì)發(fā)現(xiàn)在main()方法里是使用泛型類型Gen<String>,便不再需要強(qiáng)制類型轉(zhuǎn)換,,也就移除了運(yùn)行時(shí)的ClassCastException,。同時(shí)為了區(qū)別,在此也定義了一個(gè)沒有使用泛型類型的gen2,,這時(shí),,編譯器會(huì)彈出一個(gè)警告“Gen is a raw type,References to generic type Gen<T> should be parameterized”。當(dāng)我們不提供泛型類型時(shí),,會(huì)默認(rèn)使用Object會(huì)代替,,也是因此這樣,gen2可以設(shè)置String和Integer類型,,不過,,我們應(yīng)盡量去避免這種這種情況的出現(xiàn),如此,,便又需要用到強(qiáng)制類型轉(zhuǎn)換,,也伴隨著運(yùn)行時(shí)的ClassCastException異常。 tips:可以使用@SuppressWarnings("rawtypes")來抑制編譯器彈出警告,。 接口的泛型應(yīng)用和類的泛型應(yīng)用很類似,,如下: public interface List <E> { void add(E x); Iterator<E> iterator(); } public interface Iterator<E> { E next(); boolean hasNext(); } 類似的,可以將此應(yīng)用到自定義的接口與類當(dāng)中,。另外再提一下的是,,可以使用多個(gè)泛型參數(shù)來定義接口與類,比如Map<K,V>,;同時(shí),,泛型類型也可以作為一個(gè)參數(shù)來用,如下:new HashMap<String, List<String>>(),。 三,、泛型的命名規(guī)范為了更好地去理解泛型,我們也需要去理解java泛型的命名規(guī)范,。為了與java關(guān)鍵字區(qū)別開來,,java泛型參數(shù)只是使用一個(gè)大寫字母來定義。各種常用泛型參數(shù)的意義如下:
四、泛型的方法與構(gòu)造函數(shù)有時(shí)候我們并不希望整個(gè)類都被泛型化,,這時(shí)可以只在某個(gè)方法上應(yīng)用泛型,。因?yàn)闃?gòu)造函數(shù)是一種特殊的方法,因此也可以在構(gòu)造函數(shù)上應(yīng)用泛型,。Demo GenMethod演示了如何在方法上應(yīng)用泛型和調(diào)用泛型方法,, public class GenMethod { public static <T> void fromArrayToCollection(T[] a,Collection<T> c){ for (T t : a) { c.add(t); } } public static void main(String[] args) { Object[] oa = new Object[100]; Collection<Object> co = new ArrayList<>(); GenMethod.<Object>fromArrayToCollection(oa, co); } }
public static <T> void fromArrayToCollection(T[] a,Collection<T> c){ for (T t : a) { c.add(t); c.add(new Object()); } }
GenMethod.fromArrayToCollection(oa, co),。 為了加深對(duì)編譯器推斷泛型類型的了解,再看一下如下幾個(gè)推斷: String[] sa = new String[100]; Collection<String> cs = new ArrayList<String>(); // T 推斷為String fromArrayToCollection(sa, cs); // T 推斷為Object fromArrayToCollection(sa, co); Integer[] ia = new Integer[100]; Float[] fa = new Float[100]; Number[] na = new Number[100]; Collection<Number> cn = new ArrayList<Number>(); //T 推斷為Number fromArrayToCollection(ia, cn); //T 推斷為Number fromArrayToCollection(fa, cn); //T 推斷為Number fromArrayToCollection(na, cn); //T 推斷為Object fromArrayToCollection(na, co); //編譯錯(cuò)誤,,Number與String不能兼容 fromArrayToCollection(na, cs);
四,、泛型參數(shù)的界限有時(shí)候,你會(huì)希望泛型類型只能是某一部分類型,,比如操作數(shù)據(jù)的時(shí)候,,你會(huì)希望是Number或其子類類型。這個(gè)想法其實(shí)就是給泛型參數(shù)添加一個(gè)界限,。其定義形式為: <T extends BoundingType> 此定義表示T應(yīng)該是BoundingType的子類型(subtype),。T和BoundingType可以是類,,也可以是接口。另外注意的是,,此處的”extends“表示的子類型,,不等同于繼承。 Demo: public class Box<T> { private T t; public void set(T t) { this.t = t; } public T get() { return t; } public <U extends Number> void inspect(U u) { System.out.println("T: " + t.getClass().getName()); System.out.println("U: " + u.getClass().getName()); } public static void main(String[] args) { Box<String> integerBox = new Box<>(); integerBox.set("abc"); //能通過編譯,,因?yàn)門指定為String類型 // integerBox.inspect("abc");//不能通過編譯,,因?yàn)閁必須是Number類型或其子類 integerBox.inspect(new Integer(10)); } }
public class NumberTest<T extends Integer> { private T num; public NumberTest(T num) { this.num = num;} public boolean isOdd(){ return num.intValue()%2 == 1; } //.... } 接著引入下一個(gè)問題,如何為泛型參數(shù)添加多個(gè)限制范圍,,多重限制范圍格式如下: <T extends A & B & C> 一個(gè)泛型參數(shù)可以有多重限制范圍,,使用“&”分隔。且限制范圍中之多有一個(gè)類,。如果用一個(gè)類作為限定,,它必須是限定列表中的第一個(gè)。舉例如下: Class A { /* ... */ } interface B { /* ... */ } interface C { /* ... */ } class D <T extends A & B & C> { /* ... */ }
class D <T extends B & A & C> { /* ... */ } // 無法通過編譯 五,、泛型方法與泛參界限的綜合如果說泛型方法是一個(gè)有用的工具,那泛參的界限就應(yīng)該這個(gè)工具的靈魂,,為這個(gè)工具添加了一些“行為準(zhǔn)則”,。如下:設(shè)計(jì)一個(gè)方法,統(tǒng)計(jì)在一個(gè)數(shù)組里比指定元素大的個(gè)數(shù),, public static <T> int countGreater(T[] array,T elem) { int count = 0; for (T t : array) { if (t > elem) {//編譯錯(cuò)誤 ++count; } } return count; }
public interface Comparable<T> { public int compareTo(T o); } 更改后的代碼如下: public static <T extends Comparable<T>> int countGreater(T[] array,T elem) { int count = 0; for (T t : array) { if (t.compareTo(elem) > 0) {//無編譯錯(cuò)誤 ++count; } } return count; } 除了上述方式,,也可以選擇添加界限Comparator<T,T>,只不過此界限需要兩個(gè)參數(shù)而已,Comparator的定義與使用以前已經(jīng)談過,,這里不再累述,,詳情可以點(diǎn)擊 這里。 六,、泛型,、繼承與子類型如果兩個(gè)類之間相互兼容(繼承與被繼承),那么便可以將一個(gè)類對(duì)象賦值給另一個(gè)類對(duì)象,,比如:你可以將一個(gè)String對(duì)象賦值給Object,,String是Object的子類, String someString = new String(); Object someObject = new Object(); someObject = someString; 如果你熟悉面向?qū)ο蠹夹g(shù),,會(huì)知道這是一種“is-a”關(guān)系,。String是Object的一種對(duì)象,所以上面的賦值是可以的,。同理,,Integer、Double是Number的一類對(duì)象,,下面的賦值也可以: public void someMethod(Number n) { /* ... */ } someMethod(new Integer(10)); // OK someMethod(new Double(10.1); // OK 這種“is-a”關(guān)系,,同樣也是用泛型。如果你將泛參設(shè)置Number,,那么在隨后的調(diào)用里,,只需要傳入一個(gè)數(shù)據(jù)對(duì)象就行了,如下: Box<Number> box = new Box<>(); box.add(new Integer(1)); box.add(new Double(1.0));
public void someMethod(Box<Number> n) { /*.....*/} 這個(gè)方法可以接受什么類型的參數(shù)呢,??顯然,,這個(gè)方法接受Box<Number>類型的參數(shù),??那又是否可以接受Box<Integer>或者Box<Double>類型的參數(shù)的,?,?答案是否定的,因?yàn)锽ox<Integer>與Box<Double>都不是Box<Number>的子類,。在泛型編程里,,這是一個(gè)容易混淆的概念,但又必須要懂的原理,。如下圖:
從圖可以看到,,即使Integer是Number的子類,,但Box<Integer>并不是Box<Number>的子類。Box<Integer>與Box<Number>的共同父類是Object,。換言之,,無論類A與類B是否存在關(guān)聯(lián),MyClass<A>與MyClass<B>都沒有任何關(guān)聯(lián),,其共同的父類的是Object,。那是否說,泛型就不存在子類呢,?,?這個(gè)留待解決,看完本文便可以知曉,。
七,、泛型類與子類型在談這一小節(jié)時(shí),先回顧一下泛型方法的“extends”含義,,泛型的“extends”與繼承的“extends”并不一樣,泛型的“extends”其后可以是一個(gè)類(如T extends Number),,同樣也可以是一個(gè)接口(如T extends List<T>),。泛型的”extends“代表子類型,而不是子類,,或許你可以把等其同于”extends(繼承)“和”implement的并集,。 在泛型里,也存在子類型,,前提是其泛型參數(shù)的限制并沒有改變,,可以認(rèn)為泛參沒有改變,其實(shí)就是從原來的類或接口來判斷泛型的子類型,。為了形象理解,,我們已collection類來作個(gè)例子,如:ArrayList<E> implement List<E>,,而List<E> extends Collection<E>,,那么ArrayList<String>就是List<String>的子類型,而List<String>則是Collection<String>,其關(guān)系圖如下: 深入一點(diǎn)來談,,現(xiàn)在假設(shè)需要定義自己的List接口 — PayLoadList,,其定義如下: interface PayloadList<E,P> extends List<E> { void setPayload(int index, P val); //... } 如上,則下面的樣例都是List<String>子類型,: PayloadList<String,String> PayloadList<String,Integer> PayloadList<String,Exception> 如圖: 轉(zhuǎn)自 https://blog.51cto.com/peiquan/1302898 |
|