概述
泛型误堡,即“參數(shù)化類型”古话。一提到參數(shù),最熟悉的就是定義方法時(shí)有形參锁施,然后調(diào)用此方法時(shí)傳遞實(shí)參陪踩。那么參數(shù)化類型怎么理解呢杖们?
顧名思義,就是將類型由原來的具體的類型參數(shù)化肩狂,類似于方法中的變量參數(shù)摘完,此時(shí)類型也定義成參數(shù)形式(可以稱之為類型形參),
然后在使用/調(diào)用時(shí)傳入具體的類型(類型實(shí)參)傻谁。
泛型的本質(zhì)是為了參數(shù)化類型(在不創(chuàng)建新的類型的情況下孝治,通過泛型指定的不同類型來控制形參具體限制的類型)。也就是說在泛型使用過程中审磁,
操作的數(shù)據(jù)類型被指定為一個(gè)參數(shù)谈飒,這種參數(shù)類型可以用在類、接口和方法中态蒂,分別被稱為泛型類杭措、泛型接口、泛型方法钾恢。
泛型類
定義
// 多個(gè)泛型以','分隔,如:<T,K>
public class 類名稱 <泛型標(biāo)識(shí):可以隨便寫任意標(biāo)識(shí)號手素,標(biāo)識(shí)指定的泛型的類型> {
private 泛型標(biāo)識(shí) /*(成員變量類型)*/ var;
.....
}
示例
// 此處T可以隨便寫為任意標(biāo)識(shí),常見的如T赘那、E、K氯质、V等形式的參數(shù)常用于表示泛型
// 在實(shí)例化泛型類時(shí)募舟,必須指定T的具體類型
public class Generic<T>{
// key這個(gè)成員變量的類型為T,T的類型由外部指定
private T key;
// 泛型構(gòu)造方法形參key的類型也為T,T的類型由外部指定
public Generic(T key) {
this.key = key;
}
// 泛型方法getKey的返回值類型為T闻察,T的類型由外部指定
public T getKey(){
return key;
}
}
// 泛型的類型參數(shù)只能是類類型(包括自定義類)拱礁,不能是簡單類型
// 傳入的實(shí)參類型需與泛型的類型參數(shù)類型相同,即為Integer.
Generic<Integer> genericInteger = new Generic<Integer>(123456);
// 傳入的實(shí)參類型需與泛型的類型參數(shù)類型相同辕漂,即為String.
Generic<String> genericString = new Generic<String>("key_vlaue");
Log.d("泛型測試", "key is " + genericInteger.getKey());
Log.d("泛型測試", "key is " + genericString.getKey());
==注意:==
- 泛型的類型參數(shù)只能是類類型呢灶,不能是簡單類型。
- 不能對確切的泛型類型使用instanceof操作钉嘹。如下面的操作是非法的鸯乃,編譯時(shí)會(huì)出錯(cuò)。
如:if(str instanceof Generic<String>){ }
泛型接口
定義
// 定義一個(gè)泛型接口
public interface Generator<T> {
public T next();
}
示例
正確使用
/**
* 傳入泛型實(shí)參時(shí):
* 定義一個(gè)生產(chǎn)器實(shí)現(xiàn)這個(gè)接口,雖然我們只創(chuàng)建了一個(gè)泛型接口Generator<T>
* 但是我們可以為T傳入無數(shù)個(gè)實(shí)參跋涣,形成無數(shù)種類型的Generator接口缨睡。
* 在實(shí)現(xiàn)類實(shí)現(xiàn)泛型接口時(shí),如已將泛型類型傳入實(shí)參類型陈辱,則所有使用泛型的地方都要替換成傳入的實(shí)參類型
* 即:Generator<T>奖年,public T next();中的的T都要替換成傳入的String類型。
*/
public class FruitGenerator implements Generator<String> {
private String[] fruits = new String[]{"Apple", "Banana", "Pear"};
@Override
public String next() {
Random rand = new Random();
return fruits[rand.nextInt(3)];
}
}
錯(cuò)誤使用
/**
* 未傳入泛型實(shí)參時(shí)沛贪,與泛型類的定義相同陋守,在聲明類的時(shí)候震贵,需將泛型的聲明也一起加到類中
* 即:class FruitGenerator<T> implements Generator<T>{
* 如果不聲明泛型,如:class FruitGenerator implements Generator<T>水评,編譯器會(huì)報(bào)錯(cuò):"Unknown class"
*/
class FruitGenerator<T> implements Generator<T>{
@Override
public T next() {
return null;
}
}
泛型方法
定義
/**
* 泛型方法
* @param clazz 傳入的泛型實(shí)參
* @return T 返回值為T類型
* 說明:
* 1)public 與 返回值中間<T>非常重要猩系,可以理解為聲明此方法為泛型方法。
* 2)只有聲明了<T>的方法才是泛型方法之碗,泛型類中的使用了泛型的成員方法并不是泛型方法蝙眶。
* 3)<T>表明該方法將使用泛型類型T,此時(shí)才可以在方法中使用泛型類型T褪那。
* 4)與泛型類的定義一樣幽纷,此處T可以隨便寫為任意標(biāo)識(shí),常見的如T博敬、E友浸、K、V等形式的參數(shù)常用于表示泛型偏窝。
*/
public <T> T genericMethod(Class<T> clazz) throws InstantiationException, IllegalAccessException {
T instance = clazz.newInstance();
return instance;
}
/**
* 泛型方法收恢,可變參數(shù)
* @param args 傳入的泛型可變參數(shù)
*/
public <T> void printMsg(T... args) {
for(T t : args){
Log.d("泛型測試","t is " + t);
}
}
簡單示例
// 主方法
public static void main(String[] strs) {
// 通過泛型方法創(chuàng)建對象
Object obj = genericMethod(Class.forName("android.app.Activity"));
}
深入泛型方法示例
public class GenericFruit {
class Fruit{
@Override
public String toString() {
return "fruit";
}
}
class Apple extends Fruit{
@Override
public String toString() {
return "apple";
}
}
class Person{
@Override
public String toString() {
return "Person";
}
}
class GenerateTest<T>{
public void show_1(T t){
System.out.println(t.toString());
}
// 在泛型類中聲明了一個(gè)泛型方法,使用泛型E祭往,這種泛型E可以為任意類型伦意。可以類型與T相同硼补,也可以不同驮肉。
// 由于泛型方法在聲明的時(shí)候會(huì)聲明泛型<E>,因此即使在泛型類中并未聲明泛型已骇,編譯器也能夠正確識(shí)別泛型方法中識(shí)別的泛型离钝。
public <E> void show_3(E t){
System.out.println(t.toString());
}
// 在泛型類中聲明了一個(gè)泛型方法,使用泛型T褪储,注意這個(gè)T是一種全新的類型卵渴,可以與泛型類中聲明的T不是同一種類型。
public <T> void show_2(T t){
System.out.println(t.toString());
}
}
public static void main(String[] args) {
Apple apple = new Apple();
Person person = new Person();
GenerateTest<Fruit> generateTest = new GenerateTest<Fruit>();
// apple是Fruit的子類鲤竹,所以這里可以
generateTest.show_1(apple);
// 編譯器會(huì)報(bào)錯(cuò)浪读,因?yàn)榉盒皖愋蛯?shí)參指定的是Fruit,而傳入的實(shí)參類是Person
//generateTest.show_1(person);
// 使用這兩個(gè)方法都可以成功
generateTest.show_2(apple);
generateTest.show_2(person);
// 使用這兩個(gè)方法也都可以成功
generateTest.show_3(apple);
generateTest.show_3(person);
}
}
靜態(tài)方法與泛型
public class StaticGenerator<T> {
....
....
/**
* 如果在類中定義使用泛型的靜態(tài)方法辛藻,需要添加額外的泛型聲明(將這個(gè)方法定義成泛型方法)
* 即使靜態(tài)方法要使用泛型類中已經(jīng)聲明過的泛型也不可以瑟啃。
* 如:public static void show(T t){..},此時(shí)編譯器會(huì)提示錯(cuò)誤信息:
* "StaticGenerator cannot be refrenced from static context"
*/
public static <T> void show(T t) {
}
}
泛型方法總結(jié)
泛型方法能使方法獨(dú)立于類而產(chǎn)生變化,以下是一個(gè)基本的指導(dǎo)原則:
無論何時(shí)揩尸,如果你能做到蛹屿,你就該盡量使用泛型方法。也就是說岩榆,如果使用泛型方法將整個(gè)類泛型化错负,那么就應(yīng)該使用泛型方法坟瓢。另外對于一個(gè)static的方法而已,無法訪問泛型類型的參數(shù)犹撒。所以如果static方法要使用泛型能力折联,就必須使其成為泛型方法。
泛型優(yōu)點(diǎn)
1. 類型安全
這是最顯而易見的识颊,泛型的主要目標(biāo)是提高 Java 程序的類型安全诚镰。通過使用泛型定義的變量的類型限制,可以很容易實(shí)現(xiàn)編譯期間的類型檢測祥款,避免了大量因?yàn)槭褂肙bject帶來的不必要的類型錯(cuò)誤清笨。
沒有泛型,這些對Object變量的類型假設(shè)就只存在于程序員的頭腦中(或者如果幸運(yùn)的話刃跛,還存在于代碼注釋中)抠艾,而且每次使用前還需要進(jìn)行不安全的強(qiáng)制類型轉(zhuǎn)換。
2. 代碼復(fù)用
泛型的一個(gè)很大好處就是增加了代碼的復(fù)用性桨昙,比如上面的 Generic 類检号,就能存取任意類型的對象,而不用為每種類型寫一個(gè)包裝類蛙酪。
3. 潛在的性能收益
泛型為較大的優(yōu)化帶來可能齐苛。在泛型的初始實(shí)現(xiàn)中,編譯器將強(qiáng)制類型轉(zhuǎn)換(沒有泛型的話桂塞,程序員會(huì)指定這些強(qiáng)制類型轉(zhuǎn)換)插入生成的字節(jié)碼中凹蜂。但是更多類型信息可用于編譯器這一事實(shí),為未來版本的 JVM 的優(yōu)化帶來可能藐俺。由于泛型的實(shí)現(xiàn)方式炊甲,支持泛型(幾乎)不需要 JVM 或類文件更改泥彤。所有工作都在編譯器中完成欲芹,編譯器生成類似于沒有泛型(和強(qiáng)制類型轉(zhuǎn)換)時(shí)所寫的代碼,只是更能確保類型安全而已吟吝。Java語言引入泛型的好處是安全簡單菱父。泛型的好處是在編譯的時(shí)候檢查類型安全,并且所有的強(qiáng)制轉(zhuǎn)換都是自動(dòng)和隱式的剑逃,提高代碼的重用率浙宜。
PECS原則
- 如果要從集合中讀取類型T的數(shù)據(jù),并且不能寫入蛹磺,可以使用 ? extends 通配符粟瞬;(Producer Extends)
- 如果要從集合中寫入類型T的數(shù)據(jù),并且不需要讀取萤捆,可以使用 ? super 通配符裙品;(Consumer Super)
- 如果既要存又要取俗批,那么就不要使用任何通配符。
參考: