Java-泛型機制詳解
1: 提出背景
Java集合(Collection)中元素的型別是多種多樣的,例如,有些集合中的元素是Byte型別的,而有些則可能是String型別的,等等,Java允許程式員構建一個元素型別為Object的Collection,其中的元素可以是任何型別在[Java SE](https://baike.baidu.com/item/Java SE/4662159?fromModule=lemma_inlink) 1.5之前,沒有泛型(Generics)的情況下,通過對型別Object的參考來實作引數的“任意化”,“任意化”帶來的缺點是要作顯式的強制型別轉換,而這種轉換是要求開發者對實際引數型別可以在預知的情況下進行的,對于強制型別轉換錯誤的情況,編譯器可能不提示錯誤,在運行的時候才出現例外,這是一個安全隱患,因此,為了解決這一問題,J2SE 1.5引入泛型也是自然而然的了,
2: 泛型簡介
泛型概念
? Java 泛型(generics)是 JDK 5 中引入的一個新特性, 泛型提供了編譯時型別安全檢測機制,該機制允許程式員在編譯時檢測到非法的型別,泛型的本質是引數化型別,也就是說所操作的資料型別被指定為一個引數,
泛型作用
-
第一是泛化
? 可以用T代表任意型別Java語言中引入泛型是一個較大的功能增強不僅語言、型別系統和編譯器有了較大的變化,以支持泛型,而且類別庫也進行了大翻修,所以許多重要的類,比如集合框架,都已經成為泛型化的了,這帶來了很多好處,
-
第二是型別安全
? 泛型的一個主要目標就是提高Java程式的型別安全,使用泛型可以使編譯器知道變數的型別限制,進而可以在更高程度上驗證型別假設,如果不用泛型,則必須使用強制型別轉換,而強制型別轉換不安全,在運行期可能發生ClassCast Exception例外,如果使用泛型,則會在編譯期就能發現該錯誤,
-
第三是消除強制型別轉換
泛型可以消除源代碼中的許多強制型別轉換,這樣可以使代碼更加可讀,并減少出錯的機會,
-
第四是向后兼容
? 支持泛型的Java編譯器(例如JDK1.5中的Javac)可以用來編譯經過泛型擴充的Java程式(Generics Java程式),但是現有的沒有使用泛型擴充的Java程式仍然可以用這些編譯器來編譯
3:泛型的基本使用
泛型有三種使用方式,分別為:泛型類、泛型介面、泛型方法,下面一部分例子參考與菜鳥教程網站(https://www.runoob.com/java/java-generics.html)
泛型類
泛型類的宣告和非泛型類的宣告類似,除了在類名后面添加了型別引數宣告部分,和泛型方法一樣,泛型類的型別引數宣告部分也包含一個或多個型別引數,引數間用逗號隔開,一個泛型引數,也被稱為一個型別變數,是用于指定一個泛型型別名稱的識別符號,因為他們接受一個或多個引數,這些類被稱為引數化的類或引數化的型別,
例子:
- 簡單泛型類
/**
*
* @param <T> 此處是識別符號號,T是type簡稱
*/
public class MyBox<T> {
/**
* t的型別有T指定,創建該物件的時候,指定的型別
*/
private T t;
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
}
public class MainTest {
public static void main(String[] args) {
MyBox<String> stringMyBox = new MyBox<>();
stringMyBox.add("dddd");
System.out.println(stringMyBox.get());
MyBox<Integer> integerMyBox = new MyBox<>();
integerMyBox.add(23);
System.out.println(integerMyBox.get());
}
}
- 多元泛型類
/**
*
* @param <K> 變數key的型別,由外部指定
* @param <V> 變數value的型別,由外部指定
*/
public class MyMap<K,V> {
private K key;
private V value;
public K getKey() {
return this.key;
}
public V getValue() {
return this.value;
}
public void setKey(K key) {
this.key = key;
}
public void setValue(V value ){
this.value = https://www.cnblogs.com/zwhdd/p/value;
}
}
public class MainTest {
public static void main(String[] args) {
MyMap stringStringMyMap = new MyMap<>();
stringStringMyMap.setKey("key1");
stringStringMyMap.setValue("key2");
System.out.println(stringStringMyMap.getKey()+"="+ stringStringMyMap.getValue());
MyMap<String, Integer> stringIntegerMyMap = new MyMap<>();
stringIntegerMyMap.setKey("key1");
stringIntegerMyMap.setValue(1234);
System.out.println(stringIntegerMyMap.getKey()+"="+ stringIntegerMyMap.getValue());
}
}
泛型介面
泛型介面與泛型類的定義及使用基本相同,泛型介面常被用在各種類的生產器中
例子:
當實作泛型介面的類,沒有傳入泛型實參的時候:
/**
* 定義一個泛型介面
* @param <T>
*/
public interface Generator<T> {
public T getNext();
}
/**
* 沒有傳入引數實參時,與泛型類的定義相同,在宣告類的時候,需要將泛型的宣告也一起加到類中
* @param <T>
*/
class GoodsGenerator<T> implements Generator<T> {
@Override
public T getNext() {
return null;
}
}
當實作泛型介面的類,傳入泛型實參時候:
/**
* 傳入泛型實參后,那么這個實作類里面的所有的引數都指定的型別,不能更改
* @param <Params>
*/
public class MyGoodsGenerator<Params> implements Generator<Params> {
private Params params = null;
@Override
public Params getNext() {
return params;
}
}
class Params {
private String name;
private String type;
Params() {}
Params(String name,String type) {
this.name = name;
this.type = type;
}
}
泛型方法
相對于泛型類,泛型方法就比較復雜了,
泛型類 是在實體化類的時候指明泛型的具體型別,而泛型方法,是在呼叫的時候指明泛型的具體型別
泛型方法規則
- 所有泛型方法宣告都有一個型別引數宣告部分(由尖括號分隔),該型別引數宣告部分在方法回傳型別之前(在下面例子中的
) - 每一個型別引數宣告部分包含一個或多個型別引數,引數間用逗號隔開,一個泛型引數,也被稱為一個型別變數,是用于指定一個泛型型別名稱的識別符號
- 型別引數能被用來宣告回傳值型別,并且能作為泛型方法得到的實際引數型別的占位符
- 泛型方法體的宣告和其他方法一樣,注意型別引數只能代表參考型型別,不能是原始型別(像 int、double、char 等)
Java中泛型標記符
- E - Element (在集合中使用,因為集合中存放的是元素)
- T - Type(Java 類)
- K - Key(鍵)
- V - Value(值)
- N - Number(數值型別)
- ? - 表示不確定的 java 型別
下面我們看一個例子:
/**
* 泛型方法
* 首先在public與回傳值之間的<T>必不可少,這表明這是一個泛型方法,并且宣告了一個泛型T,
* 這個T可以出現在這個泛型方法的任意位置
* @param container 用來創建泛型物件
* @param <T> 指明泛型T的具體型別
* @return
* @throws IllegalAccessException
* @throws InstantiationExceptio
*/
public <T> T showKeyName(Class<T> c) throws IllegalAccessException, InstantiationException {
//創建物件
T t = c.newInstance();
return t;
}
1: 為什么要用變數c來創建物件呢?
? 既然是泛型方法,就代表著我們不知道具體的型別是什么,也不知道構造方法如何,因此沒有辦法去new一個物件,但可以利用變數c的newInstance方法去創建物件,也就是利用反射創建物件,
? 泛型方法要求的引數是Class<T>型別,而Class.forName()方法的回傳值也是Class<T>,因此可以用Class.forName()作為引數,當然,泛型方法不是僅僅可以有一個引數Class<T>,可以根據需要添加其他引數,
好處
? 因為泛型類要在實體化的時候就指明型別,如果想換一種型別,不得不重新new一次,可能不夠靈活;而泛型方法可以在呼叫的時候指明型別,更加靈活,
泛型的上下限
先看下下面的例子
public class Entity {
}
class BaseEntity extends Entity {
}
//這兩個方法編譯不報錯
public static void funA(Entity a) {
// ...
}
public static void funB(BaseEntity b) {
funA(b);
// ...
}
//下面的funC(ListB)就會編譯報錯
public static void funC(List<Entity> Entitys) {
// ...
}
public static void funD(List<BaseEntity> BaseEntitys) {
funC(BaseEntitys);
// ...
}
如何解決這個問題: 看下面的方法
public static void funC(List<? extends Entity> Entitys) {
// ...
}
public static void funD(List<BaseEntity> BaseEntitys) {
funC(BaseEntitys);
// ...
}
? 為了解決泛型中隱含的轉換問題,Java泛型加入了型別引數的上下邊界機制,<? extends Entity>表示該型別引數可以是Entity(上邊界)或者Entity的子型別別,編譯時擦除到型別Entity,即用Entity型別代替型別引數,這種方法可以解決開始遇到的問題,編譯器知道型別引數的范圍,如果傳入的實體型別BaseEntity是在這個范圍內的話允許轉換,這時只要一次型別轉換就可以了,運行時會把物件當做Entity的實體看待,
-
上限
public class MyEntity <T extends Number>{ private T value ; public void setVar(T var){ this.value = https://www.cnblogs.com/zwhdd/p/var ; } public T getVar(){ return this.value ; } } public class MainTest { public static void main(String[] args) { MyEntitylongMyEntity = new MyEntity<>(); longMyEntity.setValue(10000000000000l); MyEntity integerMyEntity = new MyEntity<>(); integerMyEntity.setValue(1111); } } -
下限
//前面舉例的方法
public static void funC(List<? extends Entity> Entitys) {
// ...
}
總結:
在使用泛型時,可以對傳入的泛型型別實參進行上下邊界的限制,如:型別實參只準傳入某種型別的父類或某種型別的子類,
<?> 無限制通配符
<? extends E> extends 關鍵字宣告了型別的上界, 表示引數化的型別可能是所指定的型別,或者是此型別的子類
<? super E> super 關鍵字宣告了型別的下界,表示引數化的型別可能是指定的型別,或者是此型別的父類
4: 泛型的好處
Java語言中引入泛型是一個較大的功能增強,不僅語言、型別系統和編譯器有了較大的變化,以支持泛型,而且類別庫也進行了很大的改動,許多重要的類,比如集合框架,都已經成為泛型化的了,這帶來了很多好處:
-
型別安全
泛型的主要目標是提高Java程式的型別安全,通過知道使用泛型定義的變數的型別限制,編譯器可以在非常高的層次上驗證型別假設
-
消除強制型別轉換
泛型的一個附帶好處是,消除源代碼中的許多強制型別轉換,這使得代碼更加可讀,并且減少了出錯機會
-
更高的運行效率
在非泛型編程中,將筒單型別作為Object傳遞時會引起Boxing(裝箱)和Unboxing(拆箱)操作,這兩個程序都是具有很大開銷的,引入泛型后,就不必進行Boxing和Unboxing操作了,所以運行效率相對較高,特別在對集合操作非常頻繁的系統中,這個特點帶來的性能提升更加明顯
-
潛在的性能收益
泛型為較大的優化帶來可能,在泛型的初始實作中,編譯器將強制型別轉換(沒有泛型的話,Java系統開發人員會指定這些強制型別轉換)插入生成的位元組碼中,
本文來自博客園,作者:笨笨的二黃子,轉載請注明原文鏈接:https://www.cnblogs.com/zwhdd/p/17302435.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/549601.html
標籤:Java
上一篇:Go 語言體系下的微服務框架選型: Dubbo-go
下一篇:簡單模仿mybatis plus
