JavaSE基礎:泛型

胖先森發表於2018-01-31

泛型

1.引入

情景模式描述,假設完成一個學生的成績的情況:

  • 整數: math=80,english=70
  • 小數: math=85.6,englisth=77.8
  • 字串: math="66分",english="90.5分"

那麼我們應該如何處理呢?我們需要設計的成員變數可以接收不同的資料型別,我們第一想到的是Object型別

定義Student類,使用Object型別

package com.shxt.demo01;

public class Student {

    private Object math;
    private Object english;

    public Student(Object math, Object english) {
        this.math = math;
        this.english = english;
    }

    public Object getMath() {
        return math;
    }

    public void setMath(Object math) {
        this.math = math;
    }

    public Object getEnglish() {
        return english;
    }

    public void setEnglish(Object english) {
        this.english = english;
    }
}

複製程式碼

示例1:設定整型, int → 自動裝箱(Integer) → 物件上轉型Object

package com.shxt.demo01;

public class Demo01 {
    public static void main(String[] args) {
        Student student = new Student(80,70); //例項化物件並且對資料初始化
        //請注意這種強制只有在JDK7以後才能使用
        int math = (int) student.getMath();
        //JDK7以下的標準寫法
        int english = (Integer) student.getEnglish();// Object -> Integer->自動拆箱

        System.out.println("數學成績:"+math+",英語成績:"+english);
    }
}
複製程式碼

示例2:設定小數, double → 自動裝箱(Double) → 物件上轉型Object

package com.shxt.demo01;

public class Demo02 {
    public static void main(String[] args) {
        Student student = new Student(80.7,77.8); //例項化物件並且對資料初始化
        //請注意這種強制只有在JDK7以後才能使用
        double math = (double) student.getMath();
        //JDK7以下的標準寫法
        double english = (Double) student.getEnglish();// Object -> Double->自動拆箱

        System.out.println("數學成績:"+math+",英語成績:"+english);
    }
}
複製程式碼

示例3:設定字串, 字串 → 物件上轉型Object

package com.shxt.demo01;

public class Demo03 {
    public static void main(String[] args) {
        Student student = new Student("66分","90.5分"); //例項化物件並且對資料初始化
        String math = (String) student.getMath();
        String english = (String) student.getEnglish();// Object -> String

        System.out.println("數學成績:"+math+",英語成績:"+english);
    }
}
複製程式碼

程式碼分析:

上述的三個測試,我們感覺程式應該沒有什麼大的問題了,真的沒有問題了嗎? 請關注下面的測試


示例4:設定整數和字串,並且轉換為字串的操作過程

package com.shxt.demo01;

public class Demo04 {
    public static void main(String[] args) {
        Student student = new Student(80,"66分"); //例項化物件並且對資料初始化

        String math = (String) student.getMath();
        String english = (String) student.getEnglish();

        System.out.println("數學成績:"+math+",英語成績:"+english);
    }
}
複製程式碼

程式碼分析: 型別轉換錯誤

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

​ at com.shxt.demo01.Demo04.main(Demo04.java:7

說明我們上述的程式碼是存在安全隱患的,資料沒有達到統一,我們可以通過判斷改進程式碼,這個不符合我們的要求

判斷資料的型別instanceof

package com.shxt.demo01;

public class Demo05 {
    public static void main(String[] args) {
        Student student = new Student(80,"66分"); //例項化物件並且對資料初始化

        String math = null;
        if(student.getMath() instanceof Integer){
            math = String.valueOf(student.getMath());
        }

        String english = (String) student.getEnglish();

        System.out.println("數學成績:"+math+",英語成績:"+english);
    }
}
複製程式碼

對資料這樣繁瑣的判斷我們是不想看到的,我們可以通過泛型解決問題

2.初識泛型

  • 概念:泛型就是引數化型別,使用廣泛的型別
  • 起因:資料型別不明確
    • 裝入資料的型別都被當作Object型別,從而"丟失"了自己實際的型別
    • 獲取資料時往往需要轉型,效率低,容易產生錯誤
  • 作用:
    • 安全: 在編譯的時候檢查資料型別的安全
    • 省心: 所有的強制轉換都是自動和隱式的,提高程式碼的重用率

如何定義泛型?在定義類使用泛型

  • 定義格式,使用<>符號

    class 類名<字母列表>{
      修改符 字母 屬性 ;
      修飾符 建構函式(字母 引數名){
        
      }
      修飾符 返回型別 方法(字母 引數名){
        
      }
    }
    複製程式碼
  • 修改Student類如下

    package com.shxt.demo02;
    
    public class Student<T> {
        private T math;
        private T english;
    
        public Student(T math, T english) {
            this.math = math;
            this.english = english;
        }
    
        public T getMath() {
            return math;
        }
    
        public void setMath(T math) {
            this.math = math;
        }
    
        public T getEnglish() {
            return english;
        }
    
        public void setEnglish(T english) {
            this.english = english;
        }
    }
    複製程式碼
  • 泛型中常見的字母列表說明

    • T → Type表示型別
    • K,V → 代表鍵值中的Key和value
    • E → 代表Element
    • N → 代表Number數字
    • ? → 表示不確定性
  • 泛型的使用規則

    • 不能使用靜態屬性和靜態方法上
    • 使用的時候需要指定資料型別
      • 編譯時會檢查資料的型別
      • 獲取資料不再需要強制型別轉換
    • 泛型使用是不能指定基本資料型別,只能使用起對應的包裝類

示例1:設定統一的資料Float型別

package com.shxt.demo02;

public class Demo01 {
    public static void main(String[] args) {
        Student<Float> student = new Student<Float>(80.7F,77.8F);
        //獲取資料不再需要強制型別轉換,完成了資料的統一
        Float math = student.getMath();
        Float english = student.getEnglish();

        System.out.println("數學成績:"+math+",英語成績:"+english);

    }
}
複製程式碼

3.泛型的使用

(1) 泛型類

對於學生的分數,我們可能需要多樣的資料型別,那麼我們可以設定多個泛型,程式碼修改屬性

package com.shxt.demo03;

public class Student<T1,T2> {
    private T1 math;
    private T2 english;


    public Student(T1 math, T2 english) {
        this.math = math;
        this.english = english;
    }

    public T1 getMath() {
        return math;
    }

    public void setMath(T1 math) {
        this.math = math;
    }

    public T2 getEnglish() {
        return english;
    }

    public void setEnglish(T2 english) {
        this.english = english;
    }
}

複製程式碼

示例1:設定整型和字串

package com.shxt.demo03;


public class Demo01 {
    public static void main(String[] args) {
        Student<Integer,String> student = new Student<Integer,String>(80,"99分");

        int math = student.getMath();
        String english = student.getEnglish();

        System.out.println("數學成績:"+math+",英語成績:"+english);
    }
}
複製程式碼

(2) 泛型介面

使用泛型定義介面我們稱之為泛型介面,因為介面中只能有抽象方法和靜態的公共常量(不能使用泛型修飾)

interface Info<T>{        	// 在介面上定義泛型    
    public T getVar() ; 	// 定義抽象方法,抽象方法的返回值就是泛型型別    
    public void setVar(T x);  
}
複製程式碼

####A.非泛型介面

但是在使用的時候,就出現問題了,我們先看看下面這個使用方法:

class InfoImpl implements Info<String>{   	// 定義泛型介面的子類  
    private String var ;                	// 定義屬性  
    public InfoImpl(String var){        	// 通過構造方法設定屬性內容  
        this.setVar(var) ;  
    }  
    @Override  
    public void setVar(String var){  
        this.var = var ;  
    }  
    @Override  
    public String getVar(){  
        return this.var ;  
    }  
}  
  
public class Demo01{  
    public  void main(String arsg[]){  
        InfoImpl i = new InfoImpl("hanpang");  
        System.out.println(i.getVar()) ;  
    }  
};
複製程式碼

程式碼分析:

先看InfoImpl的定義:

class InfoImpl implements Info<String>{     
 …………  
}  
要清楚的一點是InfoImpl不是一個泛型類!因為他類名後沒有<T>!
複製程式碼

####B.泛型介面

在非泛型介面中,我們在類中直接把Info<T>介面給填充好了,但我們的類,是可以構造成泛型類的,那我們利用泛型類來構造填充泛型介面會是怎樣呢?

interface Info<T>{        // 在介面上定義泛型  
    public T getVar() ; // 定義抽象方法,抽象方法的返回值就是泛型型別  
    public void setVar(T var);  
}  
class InfoImpl<T> implements Info<T>{   // 定義泛型介面的子類  
    private T var ;             // 定義屬性  
    public InfoImpl(T var){     // 通過構造方法設定屬性內容  
        this.setVar(var) ;    
    }  
    public void setVar(T var){  
        this.var = var ;  
    }  
    public T getVar(){  
        return this.var ;  
    }  
}  
public class Demo01{  
    public static void main(String arsg[]){  
        InfoImpl<String> i = new InfoImpl<String>("harvic");  
        System.out.println(i.getVar()) ;  
    }  
}; 
複製程式碼

程式碼分析:

在這個類中,我們構造了一個泛型類InfoImpl,然後把泛型變數T傳給了Info,這說明介面和泛型類使用的都是同一個泛型變數。然後在使用時,就是構造一個泛型類的例項的過程,使用過程也不變。

使用泛型類來繼承泛型介面的作用就是讓使用者來定義介面所使用的變數型別,而不是像方法一那樣,在類中寫死。

那我們稍微加深點難度,構造一個多個泛型變數的類,並繼承自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) ;  
     }  
     public void setVar(U var){  
         this.var = var ;  
     }  
     public U getVar(){  
         return this.var ;  
     }  
 }  
複製程式碼

在這個例子中,我們在泛型類中定義三個泛型變數T,K,U並且把第三個泛型變數U用來填充介面Info。所以在這個例子中Info所使用的型別就是由U來決定的。 使用時是這樣的:泛型類的基本用法,不再多講,程式碼如下:

public class Demo01{  
    public  void main(String arsg[]){  
        InfoImpl<Integer,Double,String> i = new InfoImpl<Integer,Double,String>("harvic");  
        System.out.println(i.getVar()) ;  
    }  
}  
複製程式碼

###(3) 泛型方法

只能訪問物件的資訊,不能修改物件的資訊

講解了類和介面的泛型使用,下面我們再說說,怎麼單獨在一個函式裡使用泛型。比如我們在新建一個普通的類StaticFans,然後在其中定義了兩個泛型函式:

package com.shxt.demo03;

public class StaticFans {
    //靜態函式
    public static  <T> void staticMethod(T a){
        System.out.println("靜態方法: "+a.toString());
    }
    //普通函式
    public  <T> void otherMethod(T a){
        System.out.println("普通函式,無返回值: "+a.toString());
    }

    public  <T> T returnOtherMethod(T a){
        System.out.println("返回泛型型別:"+a);
        return a;
    }

}
複製程式碼

測試程式碼:

public class Demo01 {
    public static void main(String[] args) {
        StaticFans.staticMethod(100);
        StaticFans.<String>staticMethod("悟空");

        //常規方法
        StaticFans sf = new StaticFans();
        sf.otherMethod(999F);
        sf.<Double>otherMethod(100.1);

        //返回值中存在泛型
        sf.returnOtherMethod("八戒");
        sf.<Integer>returnOtherMethod(999);

    }
}
複製程式碼

程式碼說明:

方法一,隱式傳遞了T的型別,與上面一樣,不建議這麼做。 方法二,顯示將T賦值為Integer型別,這樣OtherMethod(T a)傳遞過來的引數如果不是Integer那麼編譯器就會報錯。

(4) 泛型陣列

在寫程式時,大家可能會遇到類似String[] list = new String[8];的需求,這裡可以定義String陣列,當然我們也可以定義泛型陣列,泛型陣列的定義方法為 T[],與String[]是一致的,下面看看用法:

  • 沒有泛型陣列,不能建立泛型陣列
  • 可以只有宣告,可以使用 ?
//定義  
public static <T> T[] fun1(T...arg){  // 接收可變引數    
       return arg ;            // 返回泛型陣列    
}    
//使用  
public static void main(String args[]){    
       Integer i[] = fun1(1,2,3,4,5,6) ;  
       Integer[] result = fun1(i) ;  
}
複製程式碼

4.泛型的擦除規則

我們上面的講述過程中可以看出來可以使用子類或者實現類實現泛型,那麼它們之間需要遵循一定的使用規則:

  • 子類與父類(介面)一樣使用泛型
  • 子類指定具體的型別
  • 子類與父類(介面)同時擦除泛型型別
  • 子類使用泛型,而父類(介面)擦除泛型型別
  • 注意錯誤: 不能子類擦除,而父類(介面)使用泛型

擦除後統一使用Object物件泛型

繼承演示規則過程

package com.shxt.demo04;

/**
 * 父類為泛型類
 * 1.屬性
 * 2.方法
 *
 * 1.要麼同時擦除,要麼子類大於等於父類的型別(泛型的個數)
 * 2.不能子類擦除,父類泛型
 *  A.屬性型別
 *      父類隨父類而定
 *      子類隨子類而定
 *  B.方法重寫:
 *      隨父類而定
 */
public abstract class Father<T> {
    protected T name;
    public abstract void test01(T t);
}

/**
 * 子類宣告時指定了具體型別Father<String>
 *     屬性型別為具體型別
 *     方法型別為具體型別
 */
class Child01 extends Father<String>{
    String name;

    @Override
    public void test01(String s) {

    }
}

/**
 * 子類為泛型類,需要跟父類保持一致
 * 規則可以大於等於父類的型別
 */
class Child02<T> extends Father<T>{
    T name;
    @Override
    public void test01(T t) {

    }
}

/**
 * 子類為泛型類,父類不指定型別,
 * 這個就是泛型擦除,使用Object替換
 * @param <T>
 */
class Child03<T> extends Father{
    T name;
    @Override
    public void test01(Object o) {

    }
}

/**
 * 子類與父類同時擦除
 */
class Child04 extends Father{
    String name;
    @Override
    public void test01(Object o) {

    }
}

/**
 * 錯誤:子類擦除,父類使用泛型
 */
class Child05 extends Father<T>{
    String name;
    @Override
    public void test01(T o) {

    }
}
複製程式碼

介面演示規則過程

package com.shxt.demo04;


public interface Comparable<T> {
    void compare(T t);
}

//宣告子類,指定具體型別
class Comp00 implements Comparable<String>{
    @Override
    public void compare(String s) {

    }
}

//子類與父類同時擦除,使用Object
class Comp01 implements Comparable{
    @Override
    public void compare(Object o) {

    }
}

//子類泛型,父類擦除
class Comp02<T> implements Comparable{
    @Override
    public void compare(Object o) {

    }
}

//子類泛型>=父類泛型
class Comp03<T,U> implements Comparable<T>{
    @Override
    public void compare(T t) {
        
    }
}

//錯誤程式碼,父類泛型,子類擦除
複製程式碼

5.萬用字元

萬用字元:? extends super

  • ?型別不定,使用時確定型別
  • 可以用在宣告型別以及宣告方法引數上,不能用在宣告類上
  • ? 可以接收泛型的任意型別,只能接收和檢視,不能修改
  • ? extends 泛型上限 <=
  • ? super 泛型下限 >=

###(1) 匹配任意型別的萬用字元

示例1:使用泛型傳遞資料

package com.shxt.demo05;

public class Demo01 {
    public static void main(String[] args) {
        Student<String> student  = new Student<String>();//指定String為泛型
        student.setName("胖先森"); //設定資料
        //傳遞資料
        test01(student); // 錯誤,無法傳遞資料
    }

    public static void test01(Student<Object> temp){// 此處可以接收 Object 泛型型別的Student物件
        System.out.println("內容:"+temp);
    }

}
class Student<T>{
    private T name;

    public T getName() {
        return name;
    }

    public void setName(T name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name=" + name +
                '}';
    }
}
複製程式碼

程式編譯錯誤:

Error:(8, 16) java: 不相容的型別: com.shxt.demo05.Student<java.lang.String>無法轉換為com.shxt.demo05.Student<java.lang.Object>
複製程式碼

程式碼分析:

程式中儘管String是Object類的子類,但是在進行引用資料傳遞時也是同樣無法進行操作,如果此時想讓程式正常執行,可以將test01()方法中的定義的Student修改為Student,即不指定泛型

程式碼修改,編譯通過

public class Demo01 {
    public static void main(String[] args) {
        Student<String> student  = new Student<String>();//指定String為泛型
        student.setName("胖先森"); //設定資料
        //傳遞資料
        test01(student); // 錯誤,無法傳遞資料
    }

    public static void test01(Student temp){// 此處可以接收Student物件
        System.out.println("內容:"+temp);
    }

}
複製程式碼

程式碼分析:

程式編譯時不會出現任何的錯誤,也可以正常使用,但是編寫test01()方法時Student中並沒有指定任何的泛型型別,這樣做有一些不妥當,所以為了解決這個問題,Java中引入了萬用字元"?",表示可以接收此型別的任意泛型物件(不能使用在定義類上)

使用萬用字元"?"修改程式碼

public class Demo01 {
    public static void main(String[] args) {
        Student<String> student  = new Student<String>();//指定String為泛型
        student.setName("胖先森"); //設定資料
        //傳遞資料
        test01(student);
    }

    public static void test01(Student<?> temp){// 此處可以接收Student物件
        System.out.println("內容:"+temp);
    }

}
複製程式碼

(2) 受限泛型

在引用傳遞中,在泛型操作中也可以設定一個泛型物件的範圍上限和範圍下限.

  • 上限萬用字元:使用extends關鍵字,表示這個型別必須是繼承某個類或者實現某個介面,也可以是這個類或介面本身

    • 宣告物件格式:

      類名稱<? extends 類> 物件名稱
      複製程式碼
    • 定義類格式:

      [訪問許可權] 類名稱<泛型標識 extends 類>{}
      複製程式碼

  • 下限萬用字元:使用super關鍵字,表示這個型別必須是是某個類的父類或者是某個介面的父介面,也可以是這個類或介面本身

    • 宣告物件格式:

      類名稱<? super 類> 物件名稱
      複製程式碼
    • 定義類格式:

      [訪問許可權] 類名稱<泛型標識 super 類>{}
      複製程式碼

上限萬用字元

假設一個方法中只能接收的泛型物件是陣列(Byte/Short/Integer/Long/Float/Double)型別,此時在定義方法引數接收物件時,就必須指定泛型的上限,之前將包裝類的時候我們說過Number類是數字的父類

示例1:方法定義,只能接收泛型為Number或者Number型別的子類

package com.shxt.demo06;

public class Demo01 {
    public static void main(String[] args) {
        Student<Integer> s1 = new Student<Integer>(); //宣告Integer的泛型物件
        s1.setName(999);    // 設定整數,自動裝箱
        test01(s1);     // 繼承了Number類,符合規範

        Student<Float> s2 = new Student<Float>(); //宣告Float的泛型物件
        s2.setName(888.99F);// 設定小數,自動裝箱
        test01(s2);// 繼承了Number類,符合規範

        Student<Boolean> s3 = new Student<Boolean>();//宣告Boolean的泛型物件
        s3.setName(true);// 設定布林,自動裝箱
        test01(s3);//繼承了Object,沒有繼承Number,錯誤!錯誤!錯誤!

        Student<String> s4 = new Student<String>();//宣告String的泛型物件
        s4.setName("悟空");
        test01(s4);//繼承了Object,沒有繼承Number,錯誤!錯誤!錯誤!

    }
    // 接收student物件,範圍的上限設定為Number,只能接收數字型別
    public static void test01(Student<? extends Number> temp){
        System.out.println("temp=>"+temp);
    }

}

class Student<T>{
    private T name;

    public T getName() {
        return name;
    }

    public void setName(T name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name=" + name +
                '}';
    }
}
複製程式碼

示例2:類定義,只能接收泛型為Number或者Number型別的子類

package com.shxt.demo06;

public class Demo01 {
    public static void main(String[] args) {
        Student<Integer> s1 = new Student<Integer>(); //宣告Integer的泛型物件
        s1.setName(999);    // 設定整數,自動裝箱
        test01(s1);     // 繼承了Number類,符合規範

        Student<Float> s2 = new Student<Float>(); //宣告Float的泛型物件
        s2.setName(888.99F);// 設定小數,自動裝箱
        test01(s2);// 繼承了Number類,符合規範

        Student<Boolean> s3 = new Student<Boolean>();//Boolean不是Number的子類,無法宣告


        Student<String> s4 = new Student<String>();//String不是Number的子類,無法宣告


    }
    // 接收student物件,範圍的上限設定為Number,只能接收數字型別
    public static void test01(Student<?> temp){
        System.out.println("temp=>"+temp);
    }

}

class Student<T extends Number>{ // 泛型只能是數字型別
    private T name; // 此變數的型別由外部決定
    // 返回值的型別由外部指定
    public T getName() {
        return name;
    }
    // 設定的型別由外部決定
    public void setName(T name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name=" + name +
                '}';
    }
}
複製程式碼

下限萬用字元

package com.shxt.demo06;

public class Demo01 {
    public static void main(String[] args) {
        Student<Integer> s1 = new Student<Integer>(); //宣告Integer的泛型物件
        s1.setName(999);    // 設定整數,自動裝箱
        test01(s1);         // 不是String的父類,錯誤

        Student<Boolean> s2 = new Student<Boolean>();//宣告Boolean的泛型物件
        s2.setName(true);
        test01(s2); // 不是String的父類,錯誤

        Student<String> s3 = new Student<String>();
        s3.setName("悟空");
        test01(s3); // 滿足下限的範圍

        Student<Object> s4 = new Student<Object>();
        s4.setName("悟空");
        test01(s4); // 滿足下限的範圍


    }
    // 接收student物件,範圍的下限設定為String,只能接收String或者Object
    public static void test01(Student<? super String> temp){
        System.out.println("temp=>"+temp);
    }

}

class Student<T>{ // 泛型只能是數字型別
    private T name; // 此變數的型別由外部決定
    // 返回值的型別由外部指定
    public T getName() {
        return name;
    }
    // 設定的型別由外部決定
    public void setName(T name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name=" + name +
                '}';
    }
}
複製程式碼

相關文章