首頁 > 軟體

Java 深入淺出講解泛型與包裝類

2022-04-02 13:01:57

1、什麼是泛型

泛型的本質是為了引數化型別(在不建立新的型別的情況下,通過泛型指定的不同型別來控制形參具體限制的型別)。

先看以下的例子:

我們以前學過的陣列,只能存放指定型別的元素。如:int[] array=new int[10];String[] array=new String[10];而Object類是所有類的父類別,那麼我們是否可以建立Obj陣列呢?

class Myarray{
    public Object[] array=new Object[10];
    public void setVal(int pos,Object val){
        this.array[pos]=val;
    }
    public Object getPos(int pos){
        return this.array[pos];
    }
}
public class TestDemo{
    public static void main(String[] args) {
        Myarray myarray=new Myarray();
        myarray.setVal(1,0);
        myarray.setVal(2,"shduie");//字串也可以存放
        String ret=(String)myarray.getPos(2);//雖然我們知道它是字串型別,但是還是要強制型別轉換
        System.out.println(ret);
    }
}

以上程式碼實現後,我們發現:

  • 任何型別的資料都能存放
  • 2號下標本來就是字串,但是必須進行強制型別轉換

以此引出泛型,泛型的目的就是:指定當前的容器要持有什麼型別的物件,讓編譯器自己去檢查。

2、泛型的語法

class 泛型類名稱< 型別形參列表>{

  //這裡可以使用型別引數

}

泛型的使用:

泛型類<型別實參> 變數名=new 泛型類<型別實參>(構造方法實參)

MyArray  list=new MyArray<>();

【注】

  • 型別後的<>代表預留位置,表示當前類是一個泛型類
  • 在範例化泛型時,<>中不能是簡單的型別,需要是包裝類
  • <>不參與泛型的型別組成
  • 不能new泛型型別的陣列
  • 使用泛型不需要進行強制型別轉換

一個簡單的泛型:

//此處T可以隨便寫為任意標識,常見的如T、E、K、V等形式的引數常用於表示泛型
//在範例化泛型類時,必須指定T的具體型別
public class Test<T>{ 
    //key這個成員變數的型別為T,T的型別由外部指定  
   private T key;
 
    public Test(T key) { //泛型構造方法形參key的型別也為T,T的型別由外部指定
        this.key = key;
    }
 
    public T getKey(){ //泛型方法getKey的返回值型別為T,T的型別由外部指定
        return key;
    }
}

擦除機制:編譯時會將<>中的型別擦除掉,所以<>中的東西不參與型別的組成。會將T擦除為Object。

為什麼不能範例化泛型型別的陣列?

陣列和泛型之間的一個重要區別是它們如何強制執行型別檢查。陣列在執行時儲存和檢查型別資訊,而泛型是在編譯時檢查型別錯誤。

返回的Object陣列裡面,可能存放著任何型別的資料,如string,通過int型別的陣列來接收,編譯器認為是不安全的。

3、泛型的上界

語法:

class 泛型類名稱<T extends  型別邊界>{

}

例:

public class MyArray{} //E只能是Number或Number的子類

public class MyArray<E extends Comparable<E>>{}

//E一定實現了Comparable介面的類

【注】沒有指定邊界的E,可以看作 E extends Object

4、萬用字元

? 用於在泛型的使用,即為萬用字元。萬用字元用來解決反泛型無法協變的問題。

如下兩段程式碼:

程式碼一:
public static<T> void printList1(ArrayList<T> list){
   for(T x:list){
      System.out.println(x);
   }
}
 
程式碼二:
public static<T> void printList2(ArrayList<?> list){
   for(Object x:list){
      System.out.println(x);
   }
}

程式碼2中使用了萬用字元,和程式碼1相比,此時傳入程式碼1的具體是什麼資料型別,我們是不清楚的。

(1)萬用字元的上界

語法:

<? extends 上界>

<? extends Number>//可以傳入的實參型別為Number或Number的子類

例:對於以下關係,我們需要寫一個方法來列印儲存了Animal或者Animal子類的list。

Animal
Cat extends Animal
Dog extends Animal

程式碼一:

public static <t extends Animal> void print1(List<T> list>{
    for(T animal:list){
        System.out.println(animal);//呼叫了T的toString
    }
}

此時T型別是Animal的子類或自己。

程式碼二:通過萬用字元實現

public static void print2(List<? extends Animal> list){
    for(Animal animal:list){
       Syatem.out.println(animal);//呼叫了子類的toString方法
    }
}

兩種程式碼的區別:

  • 對於泛型實現的方法來說,<T extends Animal>對T進行了限制,只能是Animal的子類。傳入Cat,就是Cat。
  • 對於萬用字元實現的方法來說,相當於對Animal進行了規定,允許傳入Animal的子類。具體哪個子類,此時並不清楚。如:傳入Cat,實際上宣告的型別是Animal,使用多型才能呼叫Cat的toString方法

萬用字元上界→父子類關係:

//需要使用萬用字元來確定父子型別

MyArrayList<? extends Number>是MyArrayList<Integer>或者MyArrayList<Double>的父類別

MyArrayList<?>是MyArrayList<? extends Number>的父類別

 ArrayList<Integer> arrayList1 = new ArrayList<>();
 ArrayList<Double> arrayList2 = new ArrayList<>();
 List<? extends Number> list = arrayList1;
 //list.add(1,1);//報錯,此時list的參照的子類物件有很多,再新增的時候,任何子型別都可以,為了安全,java不讓這樣進行新增操作。
 Number a = list.get(0);//可以通過
 Integer i = list.get(0);//編譯錯誤,只能確定是Number子類

【注】

  • 不能對其進行新增,list中儲存的可能是Number也可能是Number的子類,無法確定型別。
  • 萬用字元上界適合讀取,不適合寫入。

(2)萬用字元的下界

語法:

<? super 下界>

<? super Integer>//可以傳入的引數型別是Integer或者Integer的父類別

萬用字元下界的父子類關係:

MyArrayList<? super Integer>是MyArrayList<Intrger>的父類別型別

MyArrayList<?>是MyArrayList<? super Integer>的父類別

萬用字元下界適合寫入元素,不適合讀取。

5、包裝類

在Java中,由於基本型別不是繼承自Object,為了在泛型中可以支援基本型別,每個基本型別都對應了一個包裝類。除了Integer和Character,其餘基本型別的包裝類都是首字母大寫。

拆箱和裝箱:

int i=10;
 
//裝箱操作,新建一個Integer型別物件,將i的值放入物件的某個屬性中
Integer ii=i;  //自動裝箱
//Integer ii=Integer.valueOf(i);
Integer ij= new Integer(i);//顯示裝箱
 
//拆箱操作,將Integer物件中的值取出,放到一個基本資料型別中
int j=ii.intValue();//顯示的拆箱
int jj=ii;//隱式的拆箱

 

到此這篇關於Java 深入淺出講解泛型與包裝類的文章就介紹到這了,更多相關Java 泛型 內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com