java基礎-泛型詳解(1):基本使用
阿新 • • 發佈:2019-01-01
前言:無論何時,相信自己。
相關文章:
一、引入
1、泛型是什麼
首先告訴大家ArrayList就是泛型。那ArrayList能完成哪些想不到的功能呢?先看看下面這段程式碼:- ArrayList<String> strList = new ArrayList<String>();
- ArrayList<Integer> intList = new ArrayList<Integer>();
- ArrayList<Double> doubleList = new ArrayList<Double>();
2、沒有泛型會怎樣
先看下面這段程式碼:我們實現兩個能夠設定點座標的類,分別設定Integer型別的點座標和Float型別的點座標:
- //設定Integer型別的點座標
- class IntegerPoint{
- private Integer x ; // 表示X座標
- private Integer y ; // 表示Y座標
- publicvoid setX(Integer x){
- this.x = x ;
- }
- publicvoid setY(Integer y){
- this.y = y ;
- }
- public
- returnthis.x ;
- }
- public Integer getY(){
- returnthis.y ;
- }
- }
- //設定Float型別的點座標
- class FloatPoint{
- private Float x ; // 表示X座標
- private Float y ; // 表示Y座標
- publicvoid setX(Float x){
- this.x = x ;
- }
- publicvoid setY(Float y){
- this.y = y ;
- }
- public Float getX(){
- returnthis.x ;
- }
- public Float getY(){
- returnthis.y ;
- }
- }
答案是可以的,因為Integer和Float都是派生自Object的,我們用下面這段程式碼代替:
- class ObjectPoint{
- private Object x ;
- private Object y ;
- publicvoid setX(Object x){
- this.x = x ;
- }
- publicvoid setY(Object y){
- this.y = y ;
- }
- public Object getX(){
- returnthis.x ;
- }
- public Object getY(){
- returnthis.y ;
- }
- }
在使用的時候是這樣的:
- ObjectPoint integerPoint = new ObjectPoint();
- integerPoint.setX(new Integer(100));
- Integer integerX=(Integer)integerPoint.getX();
- integerPoint.setX(new Integer(100));
- Integer integerX=(Integer)integerPoint.getX();
同理,FloatPoint的設定和取值也是類似的,程式碼如下:
- ObjectPoint floatPoint = new ObjectPoint();
- floatPoint.setX(new Float(100.12f));
- Float floatX = (Float)floatPoint.getX();
比如我們改成下面這樣,編譯時會報錯嗎:
- ObjectPoint floatPoint = new ObjectPoint();
- floatPoint.setX(new Float(100.12f));
- String floatX = (String)floatPoint.getX();
- String floatX = (String)floatPoint.getX();
而在執行時,則不然,在執行時,floatPoint例項中明明傳進去的是Float型別的變數,非要把它強轉成String型別,肯定會報型別轉換錯誤的!
那有沒有一種辦法在編譯階段,即能合併成同一個,又能在編譯時檢查出來傳進去型別不對呢?當然,這就是泛型。
下面我們將對泛型的寫法和用法做一一講解。
二、各種泛型定義及使用
1、泛型類定義及使用
我們先看看泛型的類是怎麼定義的:- //定義
- class Point<T>{// 此處可以隨便寫識別符號號
- private T x ;
- private T y ;
- publicvoid setX(T x){//作為引數
- this.x = x ;
- }
- publicvoid setY(T y){
- this.y = y ;
- }
- public T getX(){//作為返回值
- returnthis.x ;
- }
- public T getY(){
- returnthis.y ;
- }
- };
- //IntegerPoint使用
- Point<Integer> p = new Point<Integer>() ;
- p.setX(new Integer(100)) ;
- System.out.println(p.getX());
- //FloatPoint使用
- Point<Float> p = new Point<Float>() ;
- p.setX(new Float(100.12f)) ;
- System.out.println(p.getX());
從結果中可以看到,我們實現了開篇中IntegerPoint類和FloatPoint類的效果。下面來看看泛型是怎麼定義及使用的吧。
(1)、定義泛型:Point<T>
首先,大家可以看到Point<T>,即在類名後面加一個尖括號,括號裡是一個大寫字母。這裡寫的是T,其實這個字母可以是任何大寫字母,大家這裡先記著,可以是任何大寫字母,意義是相同的。
(2)類中使用泛型
這個T表示派生自Object類的任何類,比如String,Integer,Double等等。這裡要注意的是,T一定是派生於Object類的。為方便起見,大家可以在這裡把T當成String,即String在類中怎麼用,那T在類中就可以怎麼用!所以下面的:定義變數,作為返回值,作為引數傳入的定義就很容易理解了。
- //定義變數
- private T x ;
- //作為返回值
- public T getX(){
- return x ;
- }
- //作為引數
- publicvoid setX(T x){
- this.x = x ;
- }
下面是泛型類的用法:
- //IntegerPoint使用
- Point<Integer> p = new Point<Integer>() ;
- p.setX(new Integer(100)) ;
- System.out.println(p.getX());
- //FloatPoint使用
- Point<Float> p = new Point<Float>() ;
- p.setX(new Float(100.12f)) ;
- System.out.println(p.getX());
- Point<String> p = new Point<String>() ;
而泛型類的構造則需要在類名後新增上<String>,即一對尖括號,中間寫上要傳入的型別。
因為我們構造時,是這樣的:class Point<T>,所以在使用的時候也要在Point後加上型別來定義T代表的意義。
然後在getVar()和setVar()時就沒有什麼特殊的了,直接呼叫即可。
從上面的使用時,明顯可以看出泛型的作用,在構造泛型類的例項的時候:
- //IntegerPoint使用
- Point<Integer> p = new Point<Integer>() ;
- //FloatPoint使用
- Point<Float> p = new Point<Float>() ;
前面我們提到ArrayList也是泛型,我們順便它的實現:
- publicclass ArrayList<E>{
- …………
- }
(4)使用泛型實現的優勢
相比我們開篇時使用Object的方式,有兩個優點:
(1)、不用強制轉換
- //使用Object作為返回值,要強制轉換成指定型別
- Float floatX = (Float)floatPoint.getX();
- //使用泛型時,不用強制轉換,直接出來就是String
- System.out.println(p.getVar());
可以看到,當我們構造時使用的是String,而在setVar時,傳進去Integer型別時,就會報錯。而不是像Object實現方式一樣,在執行時才會報強制轉換錯誤。
2、多泛型變數定義及字母規範
(1)、多泛型變數定義上在我們只定義了一個泛型變數T,那如果我們需要傳進去多個泛型要怎麼辦呢?
只需要在類似下面這樣就可以了:
- class MorePoint<T,U>{
- }
- class MorePoint<T,U,A,B,C>{
- }
- class MorePoint<T,U> {
- private T x;
- private T y;
- private U name;
- publicvoid setX(T x) {
- this.x = x;
- }
- public T getX() {
- returnthis.x;
- }
- …………
- publicvoid setName(U name){
- this.name = name;
- }
- public U getName() {
- returnthis.name;
- }
- }
- //使用
- MorePoint<Integer,String> morePoint = new MorePoint<Integer, String>();
- morePoint.setName("harvic");
- Log.d(TAG, "morPont.getName:" + morePoint.getName());
(2)、字母規範
在定義泛型類時,我們已經提到用於指定泛型的變數是一個大寫字母:
- class Point<T>{
- …………
- }
- E — Element,常用在java Collection裡,如:List<E>,Iterator<E>,Set<E>
- K,V — Key,Value,代表Map的鍵值對
- N — Number,數字
- T — Type,型別,如String,Integer等等
再重複一遍,使用哪個字母是沒有特定意義的!只是為了提高可讀性!!!!
3、泛型介面定義及使用
在介面上定義泛型與在類中定義泛型是一樣的,程式碼如下:
- interface Info<T>{ // 在介面上定義泛型
- public T getVar() ; // 定義抽象方法,抽象方法的返回值就是泛型型別
- publicvoid setVar(T x);
- }
與泛型類的定義一樣,也是在介面名後加尖括號;
(1)、使用方法一:非泛型類
但是在使用的時候,就出現問題了,我們先看看下面這個使用方法:
- class InfoImpl implements Info<String>{ // 定義泛型介面的子類
- private String var ; // 定義屬性
- public InfoImpl(String var){ // 通過構造方法設定屬性內容
- this.setVar(var) ;
- }
- @Override
- publicvoid setVar(String var){
- this.var = var ;
- }
- @Override
- public String getVar(){
- returnthis.var ;
- }
- }
- publicclass GenericsDemo24{
- publicvoid main(String arsg[]){
- InfoImpl i = new InfoImpl("harvic");
- System.out.println(i.getVar()) ;
- }
- };
- class InfoImpl implements Info<String>{
- …………
- }
然後在在這裡我們將Info<String>中的泛型變數T定義填充為了String型別。所以在重寫時setVar()和getVar()時,IDE會也我們直接生成String型別的重寫函式。
最後在使用時,沒什麼難度,傳進去String型別的字串來構造InfoImpl例項,然後呼叫它的函式即可。
- publicclass GenericsDemo24{
- publicvoid main(String arsg[]){
- InfoImpl i = new InfoImpl("harvic");
- System.out.println(i.getVar()) ;
- }
- };
在方法一中,我們在類中直接把Info<T>介面給填充好了,但我們的類,是可以構造成泛型類的,那我們利用泛型類來構造填充泛型介面會是怎樣呢?
- interface Info<T>{ // 在介面上定義泛型
- public T getVar() ; // 定義抽象方法,抽象方法的返回值就是泛型型別
- publicvoid setVar(T var);
- }
- class InfoImpl<T> implements Info<T>{ // 定義泛型介面的子類
- private T var ; // 定義屬性
- public InfoImpl(T var){ // 通過構造方法設定屬性內容
- this.setVar(var) ;
- }
- publicvoid setVar(T var){
- this.var = var ;
- }
- public T getVar(){
- returnthis.var ;
- }
- }
- publicclass GenericsDemo24{
- publicstaticvoid main(String arsg[]){
- InfoImpl<String> i = new InfoImpl<String>("harvic");
- System.out.println(i.getVar()) ;
- }
- };
- class InfoImpl<T> implements Info<T>{ // 定義泛型介面的子類
- private T var ; // 定義屬性
- public InfoImpl(T var){ // 通過構造方法設定屬性內容
- this.setVar(var) ;
- }
- publicvoid setVar(T var){
- this.var = var ;
- }
- public T getVar(){
- returnthis.var ;
- }
- }
然後在使用時,就是構造一個泛型類的例項的過程,使用過程也不變。
- publicclass GenericsDemo24{
- publicstaticvoid main(String arsg[]){
- Info<String> i = new InfoImpl<String>("harvic");
- System.out.println(i.getVar()) ;
- }
- };
那我們稍微加深點難度,構造一個多個泛型變數的類,並繼承自Info介面:
- class InfoImpl<T,K,U> implements Info<U>{ // 定義泛型介面的子類
- private U var ;
- private T x;
- private K y;
- public InfoImpl(U var){ // 通過構造方法設定屬性內容
- this.setVar(var) ;
- }
- publicvoid setVar(U var){
- this.var = var ;
- }
- public U getVar(){
- returnthis.var ;
- }
- }
使用時是這樣的:泛型類的基本用法,不再多講,程式碼如下:
- publicclass GenericsDemo24{
- publicvoid main(String arsg[]){
- InfoImpl<Integer,Double,String> i = new InfoImpl<Integer,Double,String>("harvic");
- System.out.println(i.getVar()) ;
- }
- }
4、泛型函式定義及使用
上面我們講解了類和介面的泛型使用,下面我們再說說,怎麼單獨在一個函式裡使用泛型。比如我們在新建一個普通的類StaticFans,然後在其中定義了兩個泛型函式:- publicclass StaticFans {
- //靜態函式
- publicstatic <T> void StaticMethod(T a){
- Log.d("harvic","StaticMethod: "+a.toString());
- }
- //普通函式
- public <T> void OtherMethod(T a){
- Log.d("harvic","OtherMethod: "+a.toString());
- }
- }
使用方法如下:
- //靜態方法
- StaticFans.StaticMethod("adfdsa");//使用方法一
- StaticFans.<String>StaticMethod("adfdsa");//使用方法二
- //常規方法
- StaticFans staticFans = new StaticFans();
- staticFans.OtherMethod(new Integer(123));//使用方法一
- staticFans.<Integer>OtherMethod(new Integer(123));//使用方法二
首先,我們看靜態泛型函式的使用方法:
- StaticFans.StaticMethod("adfdsa");//使用方法一
- StaticFans.<String>StaticMethod("adfdsa");//使用方法二
方法一,可以像普通方法一樣,直接傳值,任何值都可以(但必須是派生自Object類的型別,比如String,Integer等),函式會在內部根據傳進去的引數來識別當前T的類別。但儘量不要使用這種隱式的傳遞方式,程式碼不利於閱讀和維護。因為從外觀根本看不出來你呼叫的是一個泛型函式。
方法二,與方法一不同的地方在於,在呼叫方法前加了一個<String>來指定傳給<T>的值,如果加了這個<String>來指定引數的值的話,那StaticMethod()函式裡所有用到的T型別也就是強制指定了是String型別。這是我們建議使用的方式。
同樣,常規泛型函式的使用也有這兩種方式:
- StaticFans staticFans = new StaticFans();
- staticFans.OtherMethod(new Integer(123));//使用方法一
- staticFans.<Integer>OtherMethod(new Integer(123));//使用方法二
方法一,隱式傳遞了T的型別,與上面一樣,不建議這麼做。
方法二,顯示將T賦值為Integer型別,這樣OtherMethod(T a)傳遞過來的引數如果不是Integer那麼編譯器就會報錯。
進階:返回值中存在泛型
上面我們的函式中,返回值都是void,但現實中不可能都是void,有時,我們需要將泛型變數返回,比如下面這個函式:
- publicstatic <T> List<T> parseArray(String response,Class<T> object){
- List<T> modelList = JSON.parseArray(response, object);
- return modelList;
- }
5、其它用法:Class<T>類傳遞及泛型陣列
(1)、使用Class<T>傳遞泛型類Class物件有時,我們會遇到一個情況,比如,我們在使用JSON解析字串的時候,程式碼一般是這樣的
- publicstatic List<SuccessModel> parseArray(String response){
- List<SuccessModel> modelList = JSON.parseArray(response, SuccessModel.class);
- return modelList;
- }
這段程式碼的意義就是根據SuccessModel解析出List<SuccessModel>的陣列。
- publicclass SuccessModel {
- privateboolean success;
- publicboolean isSuccess() {
- return success;
- }
- publicvoid setSuccess(boolean success) {
- this.success = success;
- }
- }
- publicstatic List<SuccessModel> parseArray(String response){
- List<SuccessModel> modelList = JSON.parseArray(response, SuccessModel.class);
- return modelList;
- }
先來看程式碼:
- publicstatic <T> List<T> parseArray(String response,Class<T> object){
- List<T> modelList = JSON.parseArray(response, object);
- return modelList;
- }
這是因為Class<T>也是一泛型,它是傳來用來裝載類的class物件的,它的定義如下:
- publicfinalclass Class<T> implements Serializable {
- …………
- }
(2)、定義泛型陣列
在寫程式時,大家可能會遇到類似String[] list = new String[8];的需求,這裡可以定義String陣列,當然我們也可以定義泛型陣列,泛型陣列的定義方法為 T[],與String[]是一致的,下面看看用法:
- //定義
- publicstatic <T> T[] fun1(T...arg){ // 接收可變引數
- return arg ; // 返回泛型陣列
- }
- //使用
- publicstaticvoid main(String args[]){
- Integer i[] = fun1(1,2,3,4,5,6) ;
- Integer[] result = fun1(i) ;
- }
- publicstatic <T> T[] fun1(T...arg){ // 接收可變引數
- return arg ; // 返回泛型陣列
- }
由於可變長引數在輸入後,會儲存在arg這個陣列中,所以,我們直接把陣列返回即可。
好了,這篇到這裡就結束了,這篇中主要講述了泛型在各方面的定義及用法,下篇,我們將講述,有關泛型限定相關的知識。
如果本文有幫到你,記得加關注哦