前言
Java泛型是一種編程語言的特性,它允許類、接口和方法在定義時使用一個或多個類型參數(shù),這些類型參數(shù)在調(diào)用時會被實(shí)際類型替換,從而增強(qiáng)了代碼的重用性和類型安全性。通過使用泛型,我們可以編寫出更加通用的代碼,同時也可以減少代碼中的強(qiáng)制類型轉(zhuǎn)換操作,提高代碼的可讀性和可維護(hù)性。比如,我們可以使用泛型實(shí)現(xiàn)一個通用的容器類,該容器類可以存儲任意類型的數(shù)據(jù),并且在使用時可以保證數(shù)據(jù)類型的安全性。泛型在 Java 中的實(shí)現(xiàn)方式是使用類型擦除技術(shù),即在編譯時將泛型類型轉(zhuǎn)換為原始類型,從而避免了類型檢查的開銷和運(yùn)行時的類型轉(zhuǎn)換。
一、Java泛型是什么?
Java 泛型(Generic)是 Java 5 中引入的一種特性,它允許類、接口和方法在定義時使用一個或多個類型參數(shù),這些類型參數(shù)在調(diào)用時會被實(shí)際類型替換,從而增強(qiáng)了代碼的重用性和類型安全性。通過使用泛型,我們可以編寫出更加通用的代碼,同時也可以減少代碼中的強(qiáng)制類型轉(zhuǎn)換操作,提高代碼的可讀性和可維護(hù)性。
在 Java 泛型中,我們可以使用以下符號來定義泛型:<T>
:表示定義一個類型參數(shù) T,可以是任何標(biāo)識符,通常用大寫字母表示,例如 List。<E>
:表示定義一個元素類型參數(shù) E,通常用于集合類中,例如 List。<K, V>
:表示定義一個鍵值對類型參數(shù) K 和 V,通常用于 Map 類中,例如 Map<K, V>。
在使用泛型時,可以將實(shí)際類型作為參數(shù)傳遞給泛型,例如 List,這樣就可以創(chuàng)建一個只能存儲 String 類型元素的列表。泛型在 Java 中的實(shí)現(xiàn)方式是使用類型擦除技術(shù),即在編譯時將泛型類型轉(zhuǎn)換為原始類型,從而避免了類型檢查的開銷和運(yùn)行時的類型轉(zhuǎn)換。
二、泛型類
泛型類型用于類的定義中,被稱為泛型類。通過泛型可以完成對一組類的操作對外開放相同的接口。最典型的就是各種容器類,如:List、Set、Map。
一個普通的泛型類:
//此處T可以隨便寫為任意標(biāo)識,常見的如T、E、K、V等形式的參數(shù)常用于表示泛型
//在實(shí)例化泛型類時,必須指定T的具體類型
public class Generic<T>{
//key這個成員變量的類型為T,T的類型由外部指定
private T key;
public Generic(T key) { //泛型構(gòu)造方法形參key的類型也為T,T的類型由外部指定
this.key = key;
}
public T getKey(){ //泛型方法getKey的返回值類型為T,T的類型由外部指定
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());
12-27 09:20:04.432 13063-13063/? D/泛型測試: key is 123456
12-27 09:20:04.432 13063-13063/? D/泛型測試: key is key_vlaue
定義的泛型類,就一定要傳入泛型類型實(shí)參么?并不是這樣,在使用泛型的時候如果傳入泛型實(shí)參,則會根據(jù)傳入的泛型實(shí)參做相應(yīng)的限制,此時泛型才會起到本應(yīng)起到的限制作用。如果不傳入泛型類型實(shí)參的話,在泛型類中使用泛型的方法或成員變量定義的類型可以為任何的類型。
舉例:
Generic generic = new Generic("111111");
Generic generic1 = new Generic(4444);
Generic generic2 = new Generic(55.55);
Generic generic3 = new Generic(false);
Log.d("泛型測試","key is " + generic.getKey());
Log.d("泛型測試","key is " + generic1.getKey());
Log.d("泛型測試","key is " + generic2.getKey());
Log.d("泛型測試","key is " + generic3.getKey());
運(yùn)行結(jié)果:
D/泛型測試: key is 111111
D/泛型測試: key is 4444
D/泛型測試: key is 55.55
D/泛型測試: key is false
三、泛型接口
Java泛型接口是指在接口中定義泛型類型的接口,可以在接口中使用泛型類型來定義方法參數(shù)類型、返回值類型或者接口自身的類型。
下面是一個簡單的泛型接口示例:
public interface MyList<T> {
void add(T element);
T get(int index);
int size();
}
上面的示例中,定義了一個 MyList 接口,使用泛型類型 T 來表示列表中元素的類型,包含了三個方法:add、get 和 size,分別用于向列表中添加元素、獲取列表中指定位置的元素和獲取列表的大小。
下面是一個實(shí)現(xiàn) MyList 接口的示例代碼:
public class ArrayList<T> implements MyList<T> {
private T[] array;
private int size;
public ArrayList() {
array = (T[]) new Object[10];
size = 0;
}
@Override
public void add(T element) {
if (size == array.length) {
T[] newArray = (T[]) new Object[array.length * 2];
System.arraycopy(array, 0, newArray, 0, array.length);
array = newArray;
}
array[size++] = element;
}
@Override
public T get(int index) {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException();
}
return array[index];
}
@Override
public int size() {
return size;
}
}
上面的示例中,定義了一個 ArrayList 類,實(shí)現(xiàn)了 MyList 接口,使用泛型類型 T 來表示列表中元素的類型。在 add 方法中,如果列表已滿,則擴(kuò)容數(shù)組;在 get 方法中,如果索引超出列表范圍,則拋出 IndexOutOfBoundsException 異常。
使用泛型接口可以使代碼更加通用和靈活,可以適用于各種不同類型的數(shù)據(jù)結(jié)構(gòu)和算法。
四、泛型方法
4.1泛型方法
泛型類,是在實(shí)例化類的時候指明泛型的具體類型;泛型方法,是在調(diào)用方法的時候指明泛型的具體類型 。
/**
* 泛型方法的基本介紹
* @param tClass 傳入的泛型實(shí)參
* @return T 返回值為T類型
* 說明:
* 1)public 與 返回值中間<T>非常重要,可以理解為聲明此方法為泛型方法。
* 2)只有聲明了<T>的方法才是泛型方法,泛型類中的使用了泛型的成員方法并不是泛型方法。
* 3)<T>表明該方法將使用泛型類型T,此時才可以在方法中使用泛型類型T。
* 4)與泛型類的定義一樣,此處T可以隨便寫為任意標(biāo)識,常見的如T、E、K、V等形式的參數(shù)常用于表示泛型。
*/
public <T> T genericMethod(Class<T> tClass)throws InstantiationException ,
IllegalAccessException{
T instance = tClass.newInstance();
return instance;
}
Object obj = genericMethod(Class.forName("com.test.test"));
Java 泛型方法允許您在調(diào)用時指定數(shù)據(jù)類型,而不是在編寫方法時指定它們。這使您的代碼更加靈活和通用,因?yàn)槟梢允褂枚喾N不同類型的數(shù)據(jù)。
下面是一個簡單的示例代碼來說明 Java 泛型方法的使用:
public class GenericMethodExample {
// 泛型方法,輸入?yún)?shù)類型為 T,返回值類型也為 T
public static <T> T genericMethod(T data) {
return data;
}
public static void main(String[] args) {
// 調(diào)用泛型方法并指定參數(shù)類型為 String
String strData = genericMethod("Hello World");
System.out.println("String Data : " + strData);
// 調(diào)用泛型方法并指定參數(shù)類型為 Integer
Integer intData = genericMethod(123);
System.out.println("Integer Data : " + intData);
// 調(diào)用泛型方法并指定參數(shù)類型為 Double
Double dblData = genericMethod(10.5);
System.out.println("Double Data : " + dblData);
}
}
運(yùn)行結(jié)果:
String Data : Hello World
Integer Data : 123
Double Data : 10.5
在上面的示例代碼中,我們定義了一個名為genericMethod的泛型方法。該方法具有一個T類型參數(shù)data,該參數(shù)指定了輸入?yún)?shù)和返回值的數(shù)據(jù)類型。在genericMethod方法中,我們使用不同的數(shù)據(jù)類型調(diào)用 genericMethod方法,例如 字符串,整型 和浮點(diǎn)型。
4.2泛型方法的基本用法
public class GenericTest {
//這個類是個泛型類,在上面已經(jīng)介紹過
public class Generic<T>{
private T key;
public Generic(T key) {
this.key = key;
}
//我想說的其實(shí)是這個,雖然在方法中使用了泛型,但是這并不是一個泛型方法。
//這只是類中一個普通的成員方法,只不過他的返回值是在聲明泛型類已經(jīng)聲明過的泛型。
//所以在這個方法中才可以繼續(xù)使用 T 這個泛型。
public T getKey(){
return key;
}
/**
* 這個方法顯然是有問題的,在編譯器會給我們提示這樣的錯誤信息"cannot reslove symbol E"
* 因?yàn)樵陬惖穆暶髦胁⑽绰暶鞣盒虴,所以在使用E做形參和返回值類型時,編譯器會無法識別。
public E setKey(E key){
this.key = keu
}
*/
}
/**
* 這才是一個真正的泛型方法。
* 首先在public與返回值之間的<T>必不可少,這表明這是一個泛型方法,并且聲明了一個泛型T
* 這個T可以出現(xiàn)在這個泛型方法的任意位置.
* 泛型的數(shù)量也可以為任意多個
* 如:public <T,K> K showKeyName(Generic<T> container){
* ...
* }
*/
public <T> T showKeyName(Generic<T> container){
System.out.println("container key :" + container.getKey());
//當(dāng)然這個例子舉的不太合適,只是為了說明泛型方法的特性。
T test = container.getKey();
return test;
}
//這也不是一個泛型方法,這就是一個普通的方法,只是使用了Generic<Number>這個泛型類做形參而已。
public void showKeyValue1(Generic<Number> obj){
Log.d("泛型測試","key value is " + obj.getKey());
}
//這也不是一個泛型方法,這也是一個普通的方法,只不過使用了泛型通配符?
//同時這也印證了泛型通配符章節(jié)所描述的,?是一種類型實(shí)參,可以看做為Number等所有類的父類
public void showKeyValue2(Generic<?> obj){
Log.d("泛型測試","key value is " + obj.getKey());
}
/**
* 這個方法是有問題的,編譯器會為我們提示錯誤信息:"UnKnown class 'E' "
* 雖然我們聲明了<T>,也表明了這是一個可以處理泛型的類型的泛型方法。
* 但是只聲明了泛型類型T,并未聲明泛型類型E,因此編譯器并不知道該如何處理E這個類型。
public <T> T showKeyName(Generic<E> container){
...
}
*/
/**
* 這個方法也是有問題的,編譯器會為我們提示錯誤信息:"UnKnown class 'T' "
* 對于編譯器來說T這個類型并未項目中聲明過,因此編譯也不知道該如何編譯這個類。
* 所以這也不是一個正確的泛型方法聲明。
public void showkey(T genericObj){
}
*/
public static void main(String[] args) {
}
}
4.3類中的泛型方法
當(dāng)然這并不是泛型方法的全部,泛型方法可以出現(xiàn)雜任何地方和任何場景中使用。但是有一種情況是非常特殊的,當(dāng)泛型方法出現(xiàn)在泛型類中時,我們再通過一個例子看一下
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());
}
//在泛型類中聲明了一個泛型方法,使用泛型E,這種泛型E可以為任意類型??梢灶愋团cT相同,也可以不同。
//由于泛型方法在聲明的時候會聲明泛型<E>,因此即使在泛型類中并未聲明泛型,編譯器也能夠正確識別泛型方法中識別的泛型。
public <E> void show_3(E t){
System.out.println(t.toString());
}
//在泛型類中聲明了一個泛型方法,使用泛型T,注意這個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);
//編譯器會報錯,因?yàn)榉盒皖愋蛯?shí)參指定的是Fruit,而傳入的實(shí)參類是Person
//generateTest.show_1(person);
//使用這兩個方法都可以成功
generateTest.show_2(apple);
generateTest.show_2(person);
//使用這兩個方法也都可以成功
generateTest.show_3(apple);
generateTest.show_3(person);
}
}
4.4泛型方法與可變參數(shù)
看一個泛型方法與可變參數(shù)的例子
public <T> void printMsg( T... args){
for(T t : args){
Log.d("泛型測試","t is " + t);
}
}
printMsg("111",222,"aaaa","2323.4",55.55);
4.5 靜態(tài)方法與泛型
靜態(tài)方法有一種情況需要注意一下,那就是在類中的靜態(tài)方法使用泛型:靜態(tài)方法無法訪問類上定義的泛型;如果靜態(tài)方法操作的引用數(shù)據(jù)類型不確定的時候,必須要將泛型定義在方法上。
即:如果靜態(tài)方法要使用泛型的話,必須將靜態(tài)方法也定義成泛型方法 。
public class StaticGenerator<T> {
....
....
/**
* 如果在類中定義使用泛型的靜態(tài)方法,需要添加額外的泛型聲明(將這個方法定義成泛型方法)
* 即使靜態(tài)方法要使用泛型類中已經(jīng)聲明過的泛型也不可以。
* 如:public static void show(T t){..},此時編譯器會提示錯誤信息:
"StaticGenerator cannot be refrenced from static context"
*/
public static <T> void show(T t){
}
}
五、泛型通配符
我們知道Ingeter
是Number
的一個子類,同時在特性章節(jié)中我們也驗(yàn)證過Generic<Ingeter>
與Generic<Number>
實(shí)際上是相同的一種基本類型。那么問題來了,在使用Generic<Number>
作為形參的方法中,能否使用Generic<Ingeter>
的實(shí)例傳入呢?在邏輯上類似于Generic<Number>
和Generic<Ingeter>
是否可以看成具有父子關(guān)系的泛型類型呢?
為了弄清楚這個問題,我們使用Generic<T>
這個泛型類繼續(xù)看下面的例子:
public void showKeyValue1(Generic<Number> obj){
Log.d("泛型測試","key value is " + obj.getKey());
}
Generic<Integer> gInteger = new Generic<Integer>(123);
Generic<Number> gNumber = new Generic<Number>(456);
showKeyValue(gNumber);
// showKeyValue這個方法編譯器會為我們報錯:Generic<java.lang.Integer>
// cannot be applied to Generic<java.lang.Number>
// showKeyValue(gInteger);
通過提示信息我們可以看到Generic<Integer>
不能被看作為Generic<Number>
的子類。由此可以看出:同一種泛型可以對應(yīng)多個版本(因?yàn)閰?shù)類型是不確定的),不同版本的泛型類實(shí)例是不兼容的。
回到上面的例子,如何解決上面的問題?總不能為了定義一個新的方法來處理Generic<Integer>
類型的類,這顯然與java中的多臺理念相違背。因此我們需要一個在邏輯上可以表示同時是Generic<Integer>
和Generic<Number>
父類的引用類型。由此類型通配符應(yīng)運(yùn)而生。
我們可以將上面的方法改一下:
public void showKeyValue1(Generic<?> obj){
Log.d("泛型測試","key value is " + obj.getKey());
}
類型通配符一般是使用?代替具體的類型實(shí)參,注意了,此處’?’是類型實(shí)參,而不是類型形參 。再直白點(diǎn)的意思就是,此處的?和Number、String、Integer一樣都是一種實(shí)際的類型,可以把?看成所有類型的父類。是一種真實(shí)的類型。
可以解決當(dāng)具體類型不確定的時候,這個通配符就是 ? ;當(dāng)操作類型時,不需要使用類型的具體功能時,只使用Object類中的功能。那么可以用 ? 通配符來表未知類型。
六、 泛型上下邊界
在使用泛型的時候,我們還可以為傳入的泛型類型實(shí)參進(jìn)行上下邊界的限制,如:類型實(shí)參只準(zhǔn)傳入某種類型的父類或某種類型的子類。
Java的上界限制:
public void showKeyValue1(Generic<? extends Number> obj){
Log.d("泛型測試","key value is " + obj.getKey());
}
Generic<String> generic1 = new Generic<String>("11111");
Generic<Integer> generic2 = new Generic<Integer>(2222);
Generic<Float> generic3 = new Generic<Float>(2.4f);
Generic<Double> generic4 = new Generic<Double>(2.56);
//這一行代碼編譯器會提示錯誤,因?yàn)镾tring類型并不是Number類型的子類
//showKeyValue1(generic1);
showKeyValue1(generic2);
showKeyValue1(generic3);
showKeyValue1(generic4);
如果我們把泛型類的定義也改一下:
public class Generic<T extends Number>{
private T key;
public Generic(T key) {
this.key = key;
}
public T getKey(){
return key;
}
}
//這一行代碼也會報錯,因?yàn)镾tring不是Number的子類
Generic<String> generic1 = new Generic<String>("11111");
再來一個泛型方法的例子:
//在泛型方法中添加上下邊界限制的時候,必須在權(quán)限聲明與返回值之間的<T>上添加上下邊界,即在泛型聲明的時候添加
//public <T> T showKeyName(Generic<T extends Number> container),編譯器會報錯:"Unexpected bound"
public <T extends Number> T showKeyName(Generic<T> container){
System.out.println("container key :" + container.getKey());
T test = container.getKey();
return test;
}
Java的下界限制:
下界限制:使用super關(guān)鍵字限制泛型類型的下界。
如:class MyClass<T super Number>{}
通過上面的兩個例子可以看出:泛型的上下邊界添加,必須與泛型的聲明在一起 。
七、泛型的類型擦除
Java的泛型是偽泛型,為什么說Java的泛型是偽泛型呢?因?yàn)樵诰幾g期間,所有的泛型信息都會被擦除掉,我們常稱為泛型擦除。
@Test
public void test() {
List<String> stringList = new ArrayList<String>();
stringList.add("泛型");
List<Integer> integerList = new ArrayList<Integer>();
integerList.add(1);
System.out.println(stringList.getClass() == integerList.getClass());
}
//結(jié)果
true
定義了兩個List,不過一個是List泛型類型,只能存儲字符串。一個是List泛型類型,只能存儲整型。最后,我們通過stringList對象和integerList對象的getClass方法獲取它們的類的信息,最后發(fā)現(xiàn)結(jié)果為true。說明泛型類型String和Integer都被擦除掉了,只剩下了原始類型。文章來源:http://www.zghlxwxcb.cn/news/detail-767162.html
八、總結(jié)
Java泛型是Java SE 5中引入的一項新特性,它提供了一種在編譯時期進(jìn)行類型檢查和類型推斷的機(jī)制,以避免在運(yùn)行時期出現(xiàn)類型轉(zhuǎn)換異常的問題。以下是Java泛型的總結(jié):文章來源地址http://www.zghlxwxcb.cn/news/detail-767162.html
- 泛型類:定義一個泛型類,可以在類名后面加上一對尖括號,括號中放置類型參數(shù)。如:
class MyClass<T>{}
- 泛型方法:定義一個泛型方法,可以在方法返回類型前面加上類型參數(shù)。如:
public <T> T myMethod(T t){}
- 通配符:使用通配符“?”表示未知類型。如:
List<?> myList
- 上界限制:使用extends關(guān)鍵字限制泛型類型的上界。如:
class MyClass<T extends Number>{}
- 下界限制:使用super關(guān)鍵字限制泛型類型的下界。如:
class MyClass<T super Number>{}
- 類型擦除:在編譯時期,泛型類型會被擦除為原始類型,如會被擦除為。
List<String>List
- 限制泛型數(shù)組的創(chuàng)建:由于類型擦除的存在,無法直接創(chuàng)建泛型數(shù)組。如:
List<String>[] listArray = new List<String>[10]
是錯誤的;
總之,Java泛型提供了一種類型安全、靈活的編程方式,有助于提高代碼的可讀性、可維護(hù)性和可復(fù)用性。
到了這里,關(guān)于Java的泛型詳解的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!