java泛型使用例項
阿新 • • 發佈:2019-02-06
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
*/
泛型是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
*/