1. 程式人生 > >java泛型使用例項

java泛型使用例項

1、什麼是java泛型?
泛型是Java SE 1.5的新特性,泛型的本質是引數化型別,也就是說所操作的資料型別被指定為一個引數。這種引數型別可以用在類、介面和方法的建立中,分別稱為泛型類、泛型介面、泛型方法。
2、為什麼需要泛型?
Java語言引入泛型的好處是安全簡單。可以將執行時錯誤提前到編譯時錯誤。
在java SE 1.5之前,沒有泛型的情況的下,通過對型別Object的引用來實現引數的“任意化”,“任意化”帶來的缺點是要做顯式的強制型別轉換,而這種轉換是要求開發者對實際引數型別可以預知的情況下進行的。對於強制型別轉換錯誤的情況,編譯器可能不提示錯誤,在執行的時候才出現異常,這是一個安全隱患。泛型的好處是在編譯的時候檢查型別安全,並且所有的強制轉換都是自動和隱式的,提高程式碼的重用率。
示例程式碼如下:
[java] view plain copy
package Generics;  
  
class SimpleGen {  
    private Object ob;  
  
    public SimpleGen(Object ob) {  
        this.ob = ob;  
    }  
  
    public Object getOb() {  
        return ob;  
    }  
  
    public void setOb(Object ob) {  
        this.ob = ob;  
    }  
  
    public void showType() {  
        System.out.println(ob.getClass().getName());  
    }  
}  
  
public class SimpleGenDemo1 {  
    public static void main(String[] args) {  
        SimpleGen sg = new SimpleGen(new Integer(99));  
        sg.showType();  
        int i = (Integer) sg.getOb(); //強制型別轉換,系統可能會拋一個ClassCastException異常資訊  
        System.out.println("value = " + i);  
        SimpleGen sg2 = new SimpleGen("掌上洪城");  
        sg2.showType();               //強制型別轉換,系統可能會拋一個ClassCastException異常資訊  
        String str = (String) sg2.getOb();  
        System.out.println("value = " + str);  
    }  
}  
/* 輸出結果為: 
java.lang.Integer 
value = 99 
java.lang.String 
value = 掌上洪城 
*/  


3、什麼是元組類庫,怎麼用?
3.1、為什麼使用元組tuple?
元組和列表list一樣,都可能用於資料儲存,包含多個數據;但是和列表不同的是:列表只能儲存相同的資料型別,而元組不一樣,它可以儲存不同的資料型別,比如同時儲存int、string、list等,並且可以根據需求無限擴充套件。
比如說在web應用中,經常會遇到一個問題就是資料分頁問題,查詢分頁需要包含幾點資訊:當前頁數、頁大小;查詢結果返回資料為:當前頁的資料記錄,但是如果需要在前臺顯示當前頁、頁大小、總頁數等資訊的時候,就必須有另外一個資訊就是:資料記錄總數,然後根據上面的資訊進行計算得到總頁數等資訊。這個時候查詢某一頁資訊的時候需要返回兩個資料型別,一個是list(當前也的資料記錄),一個是int(記錄總數)。當然,完全可以在兩個方法、兩次資料庫連線中得到這兩個值。事實上在查詢list的時候,已經通過sql查詢得到總計錄數,如果再開一個方法,再做一次資料庫連線來查詢總計錄數,不免有點多此一舉、浪費時間、浪費程式碼、浪費生命。言重了~在這種情況下,我們就可以利用二元組,在一次資料庫連線中,得到總計錄數、當前頁記錄,並存儲到其中,簡單明瞭!
3.2、程式碼例項
[java] view plain copy
package Generics;  
  
import java.util.Date;  
  
class TwoTuple<A,B>{  
    public final A first;  
    public final B second;  
  
    public TwoTuple(A a,B b){ //這裡是括號,不是中括號  
        first = a;  
        second = b;  
    }  
    public String toString(){  
        return "(" + first + "," + second + ")";  
    }  
}  
  
class ThreeTuple<A,B,C> extends TwoTuple<A,B>{  
    private final C three;  
    public ThreeTuple(A a,B b,C c){  
        super(a,b);  
        three = c;  
    }  
    public String toString(){  
        return "(" + first + "," + second + "," + three + ")";  
    }  
}  
public class TupleTest {  
    public static void main(String[] args) {  
        TwoTuple<Integer,String> twoT = new TwoTuple<Integer,String>(99,"掌上洪城");  
        System.out.println(twoT);  
        System.out.println("======擴充套件元組類庫後======");  
        ThreeTuple<Integer,String,Date> threeT= new ThreeTuple<Integer,String,Date>(99,"掌上洪城",new Date());  
        System.out.println(threeT);  
    }  
}  
/*輸出結果為: 
 * (99,掌上洪城) 
======擴充套件元組類庫後====== 
(99,掌上洪城,Thu Apr 28 17:59:30 CST 2016) 
 * */  


4、怎麼自定義泛型介面、泛型類?
4.1 java泛型介面、泛型類簡介
泛型類中的型別引數幾乎可以用於任何可以使用介面名、類名的地方,下面的程式碼示例展示了 JDK 5.0 中集合框架中的 Map 介面的定義的一部分:
public interface Map<K, V> {
public void put(K key, V value);
public V get(K key);
}
當宣告或者例項化一個泛型的物件時,必須指定型別引數的值:
Map<String, String> map = newHashMap<String, String>();
對於常見的泛型模式,推薦的名稱是:
 
K ——鍵,比如對映的鍵。
V ——值,比如 List 和 Set 的內容,或者 Map 中的值。
E ——異常類。
T ——泛型。
泛型不是協變的
 
關於泛型的混淆,一個常見的來源就是假設它們像陣列一樣是協變的。其實它們不是協變的。List<Object> 不是 List<String> 的父型別。
 
如果 A 擴充套件 B,那麼 A 的陣列也是 B 的陣列,並且完全可以在需要 B[] 的地方使用 A[]:
 
Integer[] intArray = new Integer[10];
Number[] numberArray = intArray;
 
上面的程式碼是有效的,因為一個Integer 是 一個 Number,因而一個 Integer 陣列是 一個 Number 陣列。但是對於泛型來說則不然。下面的程式碼是無效的:
 
List<Integer> intList = newArrayList<Integer>();
List<Number> numberList = intList; //invalid
 
最初,大多數 Java 程式設計師覺得這缺少協變很煩人,或者甚至是“壞的(broken)”,但是之所以這樣有一個很好的原因。如果可以將List<Integer> 賦給 List<Number>,下面的程式碼就會違背泛型應該提供的型別安全:
 
List<Integer> intList = newArrayList<Integer>();
List<Number> numberList = intList; //invalid
numberList.add(new Float(3.1415));
 
因為 intList 和 numberList 都是有別名的,如果允許的話,上面的程式碼就會讓您將不是 Integers 的東西放進 intList 中。
4.2 程式碼例項
[java] view plain copy
package Generics;  
  
import java.util.Random;  
  
interface Generator<T> {  
    public T next();  
}  
  
class Coffee{  
    public String toString(){  
        return getClass().getSimpleName();  
    }  
}  
  
class Mocha extends Coffee{}  
class Cappuccino extends Coffee{}  
class Breve extends Coffee{}  
class Latte extends Coffee{}  
  
class CoffeeGenerator implements Generator<Coffee>{ //T為Coffee  
    private static Random rand = new Random(47);  
    private Class[] types = {Latte.class, Mocha.class, Cappuccino.class, Breve.class};  
    public Coffee next(){ //T為Coffee  
        try {  
            return (Coffee)  
                    types[rand.nextInt(types.length)].newInstance();  
        } catch (Exception e) {  
            throw new RuntimeException(e);  
        }   
    }  
}  
  
public class InterfaceGenTest {  
    public static void main(String[] args) {  
        CoffeeGenerator gen = new CoffeeGenerator();  
        for(int i=0; i<4; i++){  
            System.out.println(gen.next());  
        }  
    }  
}  
/*Cappuccino 
Mocha 
Cappuccino 
Latte*/  
5 怎麼自定義泛型方法。
5.1 泛型方法
       泛型方法使得該方法能獨立於類而產生變化。以下是一個基本的指導原則:無論何時,只要你能做到,你就應該儘量使用泛型方法。也就是說,如果使用泛型方法可以取代將整個類泛型化,那麼就應該只使用泛型方法,因為它可以使事情更清楚明白。另外,對於一個static的方法而言,無法訪問泛型類的型別引數。所以,如果static方法需要使用泛型能力,就必須使其成為泛型方法。
       要定義泛型方法,只需將泛型引數列表置於返回值之前,就像下面這樣:
5.2 程式碼示例
[java] view plain copy
package Generics;  
  
public class GenericMethods {  
//當方法操作的引用資料型別不確定的時候,可以將泛型定義在方法上  
    public <T> void f(T x){  
        System.out.println(x.getClass().getName());  
    }  
    public static void main(String[] args) {  
        GenericMethods gm = new GenericMethods();  
        gm.f(99);  
        gm.f("掌上洪城");  
        gm.f(new Integer(99));  
        gm.f(18.88);  
        gm.f('a');  
        gm.f(gm);  
    }  
}  
/* 輸出結果: 
java.lang.Integer 
java.lang.String 
java.lang.Integer 
java.lang.Double 
java.lang.Character 
Generics.GenericMethods 
 */  


5.3 可變引數與泛型方法
泛型方法與可變引數列表能很好地共存:
[java] view plain copy
package Generics;  
  
import java.util.ArrayList;  
import java.util.List;  
  
public class GenericVarargs {  
    public static <T> List<T> makeList(T... args){  
        List<T> result = new ArrayList<T>();  
        for(T item:args)  
            result.add(item);  
        return result;         
    }  
    public static void main(String[] args) {  
        List ls = makeList("A");  
        System.out.println(ls);  
        ls = makeList("A","B","C");  
        System.out.println(ls);  
        ls = makeList("ABCDEFGHIJKLMNOPQRSTUVWXYZ".split(""));  
        System.out.println(ls);  
    }  
}  
/* 
[A] 
[A, B, C] 
[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z] 
*/  


靜態方法上的泛型:靜態方法無法訪問類上定義的泛型。如果靜態方法操作的引用資料型別不確定的時候,必須要將泛型定義在方法上。
    public static<Q> void function(Q t) {
        System.out.println("function:"+t);
    }
6、怎麼構建複雜模型如list元組?
泛型的一個重要好處是能夠簡單而安全地建立複雜的模型。如List元組。
[java] view plain copy
package Generics;  
  
import java.util.ArrayList;  
  
class ThreeTuple2<A,B,C>{  
    public final A first;  
    public final B second;  
    private final C three;  
    public ThreeTuple2(A a,B b,C c){  
        first = a;  
        second = b;  
        three = c;  
    }  
    public String toString(){  
        return "(" + first + "," + second + "," + three + ")";  
    }  
}  
  
public class TupleList<A,B,C> extends ArrayList<ThreeTuple2<A,B,C>> {  
    static ThreeTuple2<Integer,String,Character> h(){  
        return new ThreeTuple2<Integer,String,Character>(99,"掌上洪城",'a');  
    }  
    public static void main(String[] args) {  
        TupleList<Integer,String,Character> ts = new TupleList<Integer,String,Character>();  
        ts.add(h());  
        ts.add(h());  
        for(ThreeTuple2<Integer,String,Character> ttp:ts)  
        System.out.println(ttp);          
    }  
}  
package Generics;  
  
import java.util.ArrayList;  
  
class ThreeTuple2<A,B,C>{  
    public final A first;  
    public final B second;  
    private final C three;  
    public ThreeTuple2(A a,B b,C c){  
        first = a;  
        second = b;  
        three = c;  
    }  
    public String toString(){  
        return "(" + first + "," + second + "," + three + ")";  
    }  
}  
  
public class TupleList<A,B,C> extends ArrayList<ThreeTuple2<A,B,C>> {  
    static ThreeTuple2<Integer,String,Character> h(){  
        return new ThreeTuple2<Integer,String,Character>(99,"掌上洪城",'a');  
    }  
    public static void main(String[] args) {  
        TupleList<Integer,String,Character> ts = new TupleList<Integer,String,Character>();  
        ts.add(h());  
        ts.add(h());  
        for(ThreeTuple2<Integer,String,Character> ttp:ts)  
        System.out.println(ttp);          
    }  
}  
/* 輸出結果為: 
(99,掌上洪城,a) 
(99,掌上洪城,a) 
 */  


7、泛型的擦除
7.1 程式碼例項:
[java] view plain copy
package generics;  
  
import java.util.*;  
  
public class ErasedTypeEquivalence {  
    public static void main(String[] args) {  
        Class c1 = new ArrayList<String>().getClass();  
        Class c2 = new ArrayList<Integer>().getClass();  
        System.out.println(c1 == c2);  
    }  
} /* 
     * Output: true 
     */// :~  


在泛型內部,無法獲得任何有關泛型引數型別的資訊。
ArrayList<String>和ArrayList<Integer>是相同的型別。
7.2 擦除的補償
要想在表示式中使用型別,需要顯式地傳遞型別的class物件。
[java] view plain copy
package generics;  
class Building {  
}  
  
class House extends Building {  
}  
  
public class ClassTypeCapture<T> {  
    Class<T> kind;  
  
    public ClassTypeCapture(Class<T> kind) {  
        this.kind = kind;  
    }  
  
    public boolean f(Object arg) {  
        return kind.isInstance(arg);  
    }  
  
    public static void main(String[] args) {  
        ClassTypeCapture<Building> ctt1 = new ClassTypeCapture<Building>(Building.class);  
        System.out.println(ctt1.f(new Building()));  
        System.out.println(ctt1.f(new House()));  
        ClassTypeCapture<House> ctt2 = new ClassTypeCapture<House>(House.class);  
        System.out.println(ctt2.f(new Building()));  
        System.out.println(ctt2.f(new House()));  
    }  
} /* 
     * Output: true true false true 
     */// :~  


8、可以建立泛型陣列嗎?相應的應用場景怎麼處理?
正如你在下面示例Erased.java中所見,不能建立泛型陣列。一般的解決方案是任何想要建立泛型陣列的地方都使用ArrayList:


[java] view plain copy
package generics;  
  
public class Erased<T> {  
    private final int SIZE = 100;  
  
    public static void f(Object arg) {  
        if (arg instanceof T) {  
        } // Cannot make a static reference to the non-static type T  
        T var = new T(); // Error  
        T[] array = new T[SIZE]; // Error  
        T[] array = (T) new Object[SIZE]; // Unchecked warning  
    }  
} /// :~  


使用ArrayList示例
[java] view plain copy
package generics;  
  
import java.util.*;  
  
public class ListOfGenerics<T> {  
    private List<T> array = new ArrayList<T>();  
  
    public void add(T item) {  
        array.add(item);  
    }  
  
    public T get(int index) {  
        return array.get(index);  
    }  
} /// :~  


9、泛型萬用字元‘?’怎麼用?
可以解決當具體型別不確定的時候,這個萬用字元就是 ?  ;當操作型別時,不需要使用型別的具體功能時,只使用Object類中的功能。那麼可以用 ? 萬用字元來表未知型別。
 
例如Class<?>classType = Class.forName("java.lang.String");
 
下面我們先看看這些程式:
//Code list 2
void TestGen0Medthod1(List l) {
 for (Object o: l)
System.out.println(o);
}
看看這個方法有沒有異議,這個方法會通過編譯的,假如你傳入String,就是這樣List<String>。
接著我們呼叫它,問題就出現了,我們將一個List<String>當作List傳給了方法,JVM會給我們一個警告,說這個破壞了型別安全,因為從List中返回的都是Object型別的,而讓我們再看看下面的方法。
//Code list 3
void TestGen0Medthod1(List<String> l) {
 for (Object o: l)
System.out.println(o);
}
因為這裡的List<String>不是List<Object>的子類,不是String與Object的關係,就是說List<String>不隸屬於list<Object>,他們不是繼承關係,所以是不行的,這裡的extends是表示限制的。
型別萬用字元是很神奇的,List<?>這個你能為他做什麼呢?怎麼都是“?”,它似乎不確定,他總不能返回一個?作為型別的資料吧,是啊他是不會返回一個“?”來問程式設計師的?JVM會做簡單的思考的,看看程式碼吧,更直觀些。
//code list 4
List<String> l1 = newArrayList<String>();
li.add(“String”);
List<?> l2 = l1;
System.out.println(l1.get(0));
這段程式碼沒問題的,l1.get(0)將返回一個Object。
10、泛型限定(上限和下限)的表示式是怎樣的?
上限:?extends E:可以接收E型別或者E的子型別物件。
下限:?super E:可以接收E型別或者E的父型別物件。
上限什麼時候用:往集合中新增元素時,既可以新增E型別物件,又可以新增E的子型別物件。為什麼?因為取的時候,E型別既可以接收E類物件,又可以接收E的子型別物件。
 
下限什麼時候用:當從集合中獲取元素進行操作的時候,可以用當前元素的型別接收,也可以用當前元素的父型別接收。
11、可以將基本型別作為泛型引數嗎?
泛型的型別引數只能是類型別(包括自定義類),不能是簡單型別(基本資料型別)。
12、什麼時候用泛型?
當介面、類及方法中的操作的引用資料型別不確定的時候,以前用的Object來進行擴充套件的,現在可以用泛型來表示。這樣可以避免強轉的麻煩,而且將執行問題轉移到的編譯時期。
泛型的細節:
1)、泛型到底代表什麼型別取決於呼叫者傳入的型別,如果沒傳,預設是Object型別;
2)、使用帶泛型的類建立物件時,等式兩邊指定的泛型必須一致;
    原因:編譯器檢查物件呼叫方法時只看變數,然而程式執行期間呼叫方法時就要考慮物件具體型別了;
3)、等式兩邊可以在任意一邊使用泛型,在另一邊不使用(考慮向後相容);
ArrayList<String>al = new ArrayList<Object>();  //錯
//要保證左右兩邊的泛型具體型別一致就可以了,這樣不容易出錯。
ArrayList<?extends Object> al = new ArrayList<String>();
al.add("aa");  //錯
//因為集合具體物件中既可儲存String,也可以儲存Object的其他子類,所以新增具體的型別物件不合適,型別檢查會出現安全問題。 ?extendsObject 代表Object的子型別不確定,怎麼能新增具體型別的物件呢?
public static voidmethod(ArrayList<? extends Object> al) {
al.add("abc");  //錯
  //只能對al集合中的元素呼叫Object類中的方法,具體子型別的方法都不能用,因為子型別不確定。
13、Java類庫中的泛型有那些?
所有的標準集合介面都是泛型化的—— Collection<V>、List<V>、Set<V> 和 Map<K,V>。類似地,集合介面的實現都是用相同型別引數泛型化的,所以HashMap<K,V> 實現 Map<K,V> 等。
除了集合類之外,Java 類庫中還有幾個其他的類也充當值的容器。這些類包括 WeakReference、SoftReference 和 ThreadLocal。
14、練習
做完這兩個練習:
練習一:寫一個使用泛型跟不使用泛型引數任意化的例子。程式碼略
練習二:修改ClassTypeCapture.java,新增一個Map<String,Class<?>>,一個addType(String typeName,Class<?>kind)方法和一個createNew()方法。createNew()將產生一個與其引數字串關聯的新例項,或者產生一條錯誤資訊。
[java] view plain copy
package Generics;  
  
import java.util.*;  
import static org.greggordon.tools.Print.*;  
  
class Building {}  
class House extends Building {}  
  
public class ClassTypeCapture21<T> {  
    Class<?> kind;  
    Map<String, Class<?>> map;  
  
    public ClassTypeCapture21(Class<?> kind) {  
        this.kind = kind;  
    }  
  
    public ClassTypeCapture21(Class<?> kind, Map<String, Class<?>> map) {  
        this.kind = kind;  
        this.map = map;  
    }  
  
    public boolean f(Object arg) {  
        return kind.isInstance(arg);  
    }  
  
    public void addType(String typename, Class<?> kind) {  
        map.put(typename, kind);  
    }  
  
    public Object createNew(String typename) throws IllegalAccessException, InstantiationException {  
        if (map.containsKey(typename))  
            return map.get(typename).newInstance();  
        System.out.println(typename + " class not available");  
        return null;  
    }  
  
    public static void main(String[] args) {  
        ClassTypeCapture21<Building> ctt1 = new ClassTypeCapture21<Building>(Building.class);  
        println(ctt1.f(new Building()));  
        println(ctt1.f(new House()));  
        ClassTypeCapture21<House> ctt2 = new ClassTypeCapture21<House>(House.class);  
        println(ctt2.f(new Building()));  
        println(ctt2.f(new House()));  
        ClassTypeCapture21<Building> ct = new ClassTypeCapture21<Building>(Building.class,  
                new HashMap<String, Class<?>>());  
        ct.addType("House", House.class);  
        ct.addType("Building", Building.class);  
        println("ct.map = " + ct.map);  
        try {  
            Building b = (Building) ct.createNew("Building");  
            House h = (House) ct.createNew("House");  
            print("b.getClass().getName(): ");  
            println(b.getClass().getName());  
            print("h.getClass().getName(): ");  
            println(h.getClass().getName());  
            print("House h is instance House: ");  
            println(h instanceof House);  
            print("House h is instance of Building: ");  
            println(h instanceof Building);  
            print("Building b is instance of House: ");  
            println(b instanceof House);  
            ct.createNew("String"); // String class not available  
        } catch (IllegalAccessException e) {  
            println("IllegalAccessException in main");  
        } catch (InstantiationException e) {  
            println("InstantiationException in main");  
        }  
    }  
}  
/* 
true 
true 
false 
true 
ct.map = {Building=class Generics.Building, House=class Generics.House} 
b.getClass().getName(): Generics.Building 
h.getClass().getName(): Generics.House 
House h is instance House: true 
House h is instance of Building: true 
Building b is instance of House: false 
String class not available 
 */