泛型的基本瞭解
泛型的本質
泛型,即”參數化類型”或者”類型參數化”。提到參數化,最熟悉的就是定義方法時有形參,然後調用此方法時傳遞實參。那麼參數化類型怎麼理解呢?顧名思義,就是將類型由原來的具體的類型參數化,類似於方法中的變量參數,此時類型也定義成參數形式(可以稱之爲類型形參),然後在調用時傳入具體的類型(類型實參)
泛型的本質是爲了參數化類型(在不創建新的類型的情況下,通過泛型指定的不同類型來控制形參具體限制的類型)。也就是說在泛型使用過程中,我們要操作的數據類型被指定爲一個參數,這種參數類型可以用在類、接口和方法中,分別被稱爲泛型類、泛型接口、泛型方法。
簡單例子
public static void demo1(){
ArrayList arrayList = new ArrayList();
arrayList.add(38);
arrayList.add("zhangsan");
arrayList.add(new Object());
for (int i = 0; i < arrayList.size(); i++) {
System.out.println(arrayList.get(i).getClass());
}
}
通過案例,我們可以知道ArrayList可以存放任意類型,但是如果我們使用泛型進行強制類型的限定則情況就不一樣了
我們在申明ArrayList時,指定了存儲類型爲String 所以在編譯階段,我們存入其他類型的元素,就會報出錯誤.即我們的申明強制的指定了ArrayList集合中存儲的元素只能是指定的String類型.這就是泛型使用的本質意義所在,指定參數化類型。
泛型的好處
通過上訴案例.我們不難分析出來泛型會給我們的代碼帶來如下好處:
- 編譯期間即確定類型,保證類型安全,避免強制類型轉換異常同時也規避了強制類型轉換帶來的問題。
- 代碼利於重用,增加通用性。
上面好處中提到了編譯期間確定類型…那運行期間呢?泛型不適用在運行期間嗎?我通過下面的內容給大家詳細分析這個問題.
泛型的類型擦除
泛型只在編譯階段有效,泛型類型在邏輯上可看成是多個不同的類型,但是其實質都是同一個數據類型編譯之後程序會採取去泛型化的措施
通過上面的例子可以證明,在編譯之後程序會採取去泛型化的措施。也就是說Java中的泛型,只在編譯階段有效。在編譯過程中,正確檢驗泛型結果後,會將泛型的相關信息擦出,並且在對象進入和離開方法的邊界處添加類型檢查和類型轉換的方法。也就是說,泛型信息不會進入到運行時階段。
泛型通配符理解
無邊界通配符
?
通配符理解
前面的內容.我們知道ArrayList 是典型的泛型類.即可以限制存儲的元素的元素類型.ArrayList指定存入的元素時string. ArrayList指定存儲元素爲Integer類型. 但是如果只有我們使用的時候才能明確知道存入類型時.我們可以使用ArrayList<?>
來表示通用的類型.
/**
* 無邊界通配符
*
* @param arrayList
*/
public static void demo5(ArrayList<?> arrayList) {
for (int i = 0; i < arrayList.size(); i++) {
System.out.println(arrayList.get(i));
}
}
ArrayList<String> arrayList1 = new ArrayList<String>();
ArrayList<Number> arrayList2 = new ArrayList<Number>();
demo5(arrayList1);
demo5(arrayList2);
案例中的 ?
代表着限定通用類型. 或者這麼理解.代表可變的參數類型.
上界通配符
? extends Number
以上圖爲例:代表從Number往下的子類或者孫類對象都是可以的
/**
* 上界通配符
*
* @param arrayList
*/
public static void demo6(ArrayList<? extends Number> arrayList) {
for (int i = 0; i < arrayList.size(); i++) {
System.out.println(arrayList.get(i));
}
}
下界通配符
? super Integer
代表從Integer 到 Object 所有對象都是可以的.
/**
* 下界通配符
*
* @param arrayList
*/
public static void demo7(ArrayList<? super Integer> arrayList) {
for (int i = 0; i < arrayList.size(); i++) {
System.out.println(arrayList.get(i));
}
}
泛型的使用
泛型跟我們的成員屬性一樣,需要先聲明才能使用.泛型的聲明採用 <> 進行聲明. 申明一般約定採用單個大寫字母表示.常用的有 K E T V 等等字符
錯誤的案例:
編譯器報錯,爲什麼呢 因爲泛型只有使用,並沒有申明.
正確的案例:
泛型類
泛型類一般指泛型的定義與類名一起.在創建實體對象時,指定泛型的類型
普通Person類:
public class Person{
private String identityCard;
public void talk(String something){
System.out.println("我正在說:"+something);
}
public Person(String identityCard){
this.identityCard = identityCard;
}
}
創建實體過程:
public static void main(String[] args) {
//Person實例過程
Person person= new Person("43333XXXXXXXXXXXXXXXXXX");
}
新加入需求,原本的身份證不僅只記錄身份證,還需要記錄地址 有效期 等等信息 這時我們需要創建一個實體IdentityCard
進行信息的保存.此時得需求改變帶來的代碼代表使得我們的靈活度並不夠.
我們可以使用泛型類進行解決…
public class Person2<T> {
private T identityCard;
public void talk(String something){
System.out.println("我正在說:"+something);
}
public Person2(T identityCard){
this.identityCard = identityCard;
}
}
Person2實例化過程:
public static void main(String[] args) {
//Person2實例過程
IdentityCard identityCard=new IdentityCard();
identityCard.setIdNumber("4323xxxxxxxxxxxxxxxxxxx");
identityCard.setAddress("湖南xxxxxxxxxxxxxxxxxx");
identityCard.setStartDate(new Date());
identityCard.setEndDate(new Date());
Person2<String> person2= new Person2("3212312312312");
Person2<IdentityCard> person3= new Person2(identityCard);
}
通過泛型類,我們可以提升我們程序固定邏輯的靈活度
泛型方法
方法的泛型有兩種:
- 實體方法
實體方法可以使用在類中定義的泛型或者方法中定義的泛型. - 靜態方法
不可以使用在類中定義的泛型,只能使用在靜態方法上定義的泛型.
案例:
public class demo1<T,K> {
public T method1(T t,K k){
return (T)null;
}
public <Y> K method2(Y y){
return (K)null;
}
public static <J> void method3(J j){
}
}
泛型接口
指在接口的定義時進行泛型的申明,接口是標準的指定者,指實現該接口的類必須實現其標準定義(即抽象方法),所以在接口上進行泛型的申明,或者說使用泛型接口,可以讓我們的程序代碼更加簡潔,更加多變.
案例:
計算接口的定義
public interface Cal {
int add(int num1,int num2);
int sub(int num1,int num2);
int mul(int num1,int num2);
int div(int num1,int num2);
}
計算接口的實現者
public class Calculator implements Cal {
@Override
public int add(int num1, int num2) {
return 0;
}
@Override
public int sub(int num1, int num2) {
return 0;
}
@Override
public int mul(int num1, int num2) {
return 0;
}
@Override
public int div(int num1, int num2) {
return 0;
}
}
但是大家會發現他的程序靈活度會很差…當前結算接口的實現者,只能滿足int類型數字計算…如果想滿足其他數字類型的計算,需在接口中定義額外的方法.此時,如果採用泛型接口即可完美解決問題.
計算泛型接口的定義:
public interface CalGeneric<U> {
public U add (U num1,U num2);
public U sub (U num1,U num2);
public U mul (U num1,U num2);
public U div (U num1,U num2);
}
計算泛型接口的實現者:
public class CalculatorGeneric2 implements CalGeneric<Float> {
@Override
public Float add(Float num1, Float num2) {
return null;
}
@Override
public Float sub(Float num1, Float num2) {
return null;
}
@Override
public Float mul(Float num1, Float num2) {
return null;
}
@Override
public Float div(Float num1, Float num2) {
return null;
}
}
如果我想實現float類型的計算需求. 我只需要實現CalGeneric<Float>
即可…可以大大的提升代碼的複用和改善程序的靈活度。