Java基礎——泛型 寬泛與約束

一、泛型

什么是泛型

Java 泛型(generics)是 JDK 5 中引入的一個新特性, 泛型提供了編譯時類型安全檢測機制,該機制允許程序員在編譯時檢測到非法的類型。
泛型的本質是參數化類型,也就是說所操作的數據類型被指定為一個參數。

為什么用泛型

提高的代碼的復用性,減少了數據的類型轉換(泛型提供了類型檢查),同時保證了類型安全。

減少類型轉換?如,使用Comparable比較時每次都需要類型強轉

泛型參數類型

規則

  1. 使用簡練的名字作為類型形參的名字,最好為單個的大寫字母,比如 T ;
  2. 如果一個泛型類有泛型方法,對于它們的類型形參來說,應避免使用相同的名字;
  3. 泛型的類型實參只能是類類型,不能是基本數據類型。

常見的參數類型起名

  • K 鍵,比如映射的鍵 key的類型
  • V 值,比如Map的值 value類型
  • E 元素,比如Set<E> Element表示元素,元素的類型
  • T 泛型,Type的意思

注意:泛型的類型名字是可以隨便寫的,上面的K,V,E,T只是我們常用的用法,有一定含義,我們對應的把T換成HAHAHA,也算是可以的。

如何了解泛型

我們可以大概從下面幾點來開展文章。

  • 泛型方法
  • 泛型類
  • 泛型接口
  • 泛型的通配符

二、泛型方法

泛型方法定義格式

 修飾符 <泛型參數列表> 返回值類型 方法名 (參數列表) {
    ……
}

.
.
我們先來看一個簡單的泛型方法

    private static <T> void inputContent(T t){
        System.out.println("打印傳入的數據:"+t);
    }

簡單規則

  • 泛型返回返回值(void也需要)之前需要有 泛型類型參數的聲明,由尖括號包括,比如<T>
    (也可以理解為帶有返回值前帶有泛型類型的都是參數方法 )
  • 形參參數可以不是泛型參數

code1 泛型方法用法參考

public class TestClass {
    public static void main(String[] args) {
        inputContent(666);
        inputContent("哈哈哈");
        new Num("張三").say();
        
        Num num = doClass(new Num("李四"));
        num.say();
    }    
    
    private static <T> void inputContent(T t){
        System.out.println("打印傳入的數據:"+t);
    }
    
    private static <T> T doClass(T t){
        return t;
    }
    
}

class Num{
    private String name;
    public Num(String str){
        this.name = str;
    }
    public void say(){
        System.out.println(name +"  調用了say方法");
    }
}

.
.
輸出

打印傳入的數據:666
打印傳入的數據:哈哈哈
張三  調用了say方法
李四  調用了say方法

.
.
code2 泛型方法,多參數類型以類型名

public class TestClass {
    public static void main(String[] args) {
        Map map = getAMap("張三", 18);        
        Set<String> set = map.keySet();
        for(String key : set){
            System.out.println("key "+key);
            System.out.println("value "+map.get(key));
        }
    }    
    private static <HAHA extends String,XIXI> Map<String,XIXI> getAMap(HAHA haha,XIXI xixi){
        Map map = new HashMap<HAHA,XIXI>();
        map.put(haha, xixi);
        return map;
    }
}

輸出

key 張三
value 18

從這個例子中,我們看到,泛型方法返回值前的<>里面的參數可以有多個,而且,參數類型名稱我們基本可以隨便起,不局限于一個字母,比如起名為HAHA,需要注意的是,

  • 我們最好起有默認含義的,比如T,K,E,V
  • <>里面的泛型參數列表已經限定了參數參數的類型,方法后面的形參的泛型參數類型只能在前面聲明的類型中選擇。

至于extends是什么怎么用,后面會涉及。

三、泛型類

格式

class 類名<泛型類型1,泛型類型2……>{
   ……
}

簡單規則

  • 在類名后面帶上<>,在<>里面聲明泛型參數列表

code3 泛型方法示例

public class TestClass {
    public static void main(String[] args) {
        Box<Integer> integerBox = new Box<Integer>();
        Box<String> stringBox = new Box<String>();
     
        integerBox.add(new Integer(10));
        stringBox.add(new String("張三"));

        System.out.println("整型值為 :"+integerBox.get());
        System.out.println("字符串為 :"+ stringBox.get());
    }    
}

class Box<T> {
      private T t;
     
      public void add(T t) {
        this.t = t;
      }
     
      public T get() {
        return t;
      }
}

.
.
輸出

整型值為 :10
字符串為 :張三

如上 Box類就是泛型類

.
.
.

泛型接口

泛型接口,就是在接口后面跟著一對<>,在<>里面放上泛型參數列表。
在接口里面的抽象方法,我們就可以結合接口定義上的泛型參數列表做一些事情。
當然,方法可以使用泛型參數類型必須是接口的聲明的泛型參數列表以內的。

code4 泛型接口的使用

public class TestClass {
    public static void main(String[] args) {
        SayClass class1 = new SayClass();
        System.out.println(class1.doSomeThing());     
    }    
}

class SayClass implements Generator<String>{
    @Override
    public String doSomeThing() {
        String  str = "被指定的類型為String";
        return str;
    }    
}

interface Generator<T> {
    // 接口的里的抽象方法繁殖可以不是T
    // 但是一般這里我們指定為T才有一定意義,不然不就白泛型了
    public T doSomeThing();
}

輸出

被指定的類型為String

.
.
.

四、泛型的限定符/通配符

四.1、泛型限定符的種類

  • <? extends Type> 子類限定/上界通配符
  • <? super Type> 父類限定 / 下界通配符
  • <?> 無限定
    Person<?> 和 Person<? extends Object> 等同.

為了演示,我們弄幾個類,一個父類Water,Water下有SofdDrink(汽水)和Juice(果汁),Juice下有分為OrangeJuice和AppleJuice類。

image.png

四.2、子類限定 extends

<? extends Type>
子類限定,有上界,代表傳入實參必須是指定類型的子類

  • 1、只讀,不可寫
  • 2、對于<? extends T>,那么限定傳入類型只能 T類或者T的子類
  • 3、<T extends Runnable & Serializable>如果給T限定多個類型,則需要使用符號&,比如<T extends Runnable & Serializable>

code5 泛型的子類限定示例

import java.util.ArrayList;
import java.util.List;

public class TestClass{
    public static void main(String[] args) {
        
        Water water = new Water("純凈水");
        SodaDrink sodaDrink = new SodaDrink("可樂");
        Juice juice = new Juice("果汁");
        OrangeJuice orangeJuice = new OrangeJuice("果粒橙");
        AppleJuice appleJuice =new AppleJuice("牛頓牌果汁");
        
        ArrayList<OrangeJuice> tempList = new  ArrayList<OrangeJuice>();
        tempList.add(orangeJuice);
        
        // ======  
        // 證明1、<? extends T>,那么限定傳入類型只能 T類或者T的子類
        ArrayList<? extends Juice> arrayList1 = new  ArrayList<Juice>();
        //ArrayList<? extends Juice> arrayList2 = new  ArrayList<Water>(); // 編譯報錯 Water不是Juice也不是Juice的子類
        ArrayList<? extends Juice> arrayList3 = new  ArrayList<OrangeJuice>();
        
        // 證明2、ArrayList<? extends Juice> arrayList1,是只讀的,不可寫入
        // 我們無法插入任何數據到arrayList1中,因為我們無法確定插入的到底是哪一種類型,是Juice? 還是OrangeJuice? 還是AppleJuice?
        // 泛型extends是不可寫的,非要寫也只能是寫入null
        //arrayList1.add(orangeJuice); // 
        
        // 泛型extends是可讀的
        ArrayList<? extends Juice> arrayList4 = tempList;
        Juice juice23 =arrayList4.get(0);
        juice23.sayName();    
    }
}

// 商店
class Shop<T>{
    private T t;
    
    public Shop(T t){
        this.t= t;
    }

    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }
}


class Water {
    private String name;

    public Water(String name) {
        this.name = name;
    }
    public void sayName() {
        System.out.println("name===: "+name);
    }
    
    public String getName(){
        return name;
    }
}

class SodaDrink extends Water{

    public SodaDrink(String name) {
        super(name);
    }
    public void sayName() {
        System.out.println("汽水/蘇打水===: "+getName());
    }    
}


class Juice extends Water{

    public Juice(String name) {
        super(name);
    }
    public void sayName() {
        System.out.println("果汁===: "+getName());
    }    
}

class OrangeJuice extends Juice{

    public OrangeJuice(String name) {
        super(name);
    }
    public void sayName() {
        System.out.println("橙汁===: "+getName());
    }
}

class AppleJuice extends Juice{

    public AppleJuice(String name) {
        super(name);
    }
    public void sayName() {
        System.out.println("蘋果汁===: "+getName());
    }    
}

.
打印

橙汁===: 果粒橙

如上,我們知道,對于作了子類限定的泛型
1、實參只能傳入限定類型的子類或者其子類
2、是只讀的,不能調用set,只能set(null)


? extends T 限定了類型為T和T的子類型

    ArrayList<? extends Juice> arrayList1 = new  ArrayList<Juice>();
    ArrayList<? extends Juice> arrayList2 = new  ArrayList<Water>(); // 編譯報錯 Water不是Juice也不是Juice的子類
    ArrayList<? extends Juice> arrayList3 = new  ArrayList<OrangeJuice>();

對于上面的代碼,arrayList1和arrayList3是合法。arrayList2會編譯報錯,因為我們限定了傳入的是Juice或者Juice的子類,但是arrayList2傳入的是Juice的父類Water。

泛型extends的讀和寫


可讀并且只能讀取到限定的類型(無法讀取到子類型)
對于ArrayList<? extends Juice> arrayList1

  • 可以從 arrayList1 中讀取到 Juice 對象, 因為 arrayList1 中包含的元素是 Juice 類型或 Juice 的子類型.
  • 無法從 arrayList1 中讀取到 OrangeJuice 類型, 因為 arrayList1 中可能保存的是 AppleJuice 類型.

.
.


不可寫,無法寫入除了null外任何元素,包括Juice類型 或者 Juice類型的子類
對于ArrayList<? extends Juice> arrayList1

  • 不能添加 Juice 到 arrayList1 中, 因為 numberArray 有可能是List<AppleJuice> 類型
  • 不能添加 OrangeJuice 到 numberArray 中, 因為 numberArray 有可能是 List<AppleJuice> 類型
  • 不能添加 AppleJuice 到 numberArray 中, 因為 numberArray 有可能是 List<OrangeJuice> 類型

四.2、父類限定 super

  • 可寫,不可讀。
  • 對于<? super T>,那么限定傳入類型只能 T類或者T的父類

code6 泛型的父類限定示例

public class TestClass{
    public static void main(String[] args) {
        
        Water water = new Water("純凈水");
        SodaDrink sodaDrink = new SodaDrink("可樂");
        Juice juice = new Juice("果汁");
        OrangeJuice orangeJuice = new OrangeJuice("果粒橙");
        AppleJuice appleJuice =new AppleJuice("牛頓牌果汁");
        
        // ======  
        // 證明1、<? super T>,那么限定傳入類型只能 T類或者T的父類
        ArrayList<? super Juice> arrayList1 = new  ArrayList<Juice>();
        ArrayList<? super Juice> arrayList2 = new  ArrayList<Water>(); 
        //ArrayList<? super Juice> arrayList3 = new  ArrayList<OrangeJuice>();// 編譯報錯 OrangeJuice不是Juice也不是Juice的子類
        
        // 證明2、ArrayList<? extends Juice> arrayList1,是可寫,不可讀。
        // 關于寫,我們只能寫入 Juice或者Juice的子類,而不能寫入Juice的父類
        arrayList1.add(juice); 
        arrayList1.add(orangeJuice); 
        //arrayList1.add(water);  // 編譯報錯
        
        arrayList2.add(juice); 
        //arrayList2.add(water);  // 編譯報錯
        
        //關于讀,我們無法讀取到類型為Juice的數據,或者Juice的值類型,因為存入的可能是Juice,也可能是Juice的子類
        // 唯一可以確定的是,讀取的出來的肯定是個Object,但是如果你非常強轉也是可以的
        Object obj =  arrayList1.get(0); // 只能確定讀出出來的是Object
        Juice jui =  (Juice) arrayList1.get(1);
        jui.sayName();

    }
}


// 商店
class Shop<T>{
    private T t;
    
    public Shop(T t){
        this.t= t;
    }

    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }
}


class Water {
    private String name;

    public Water(String name) {
        this.name = name;
    }
    public void sayName() {
        System.out.println("name===: "+name);
    }
    
    public String getName(){
        return name;
    }
}

class SodaDrink extends Water{

    public SodaDrink(String name) {
        super(name);
    }
    public void sayName() {
        System.out.println("汽水/蘇打水===: "+getName());
    }    
}


class Juice extends Water{

    public Juice(String name) {
        super(name);
    }
    public void sayName() {
        System.out.println("果汁===: "+getName());
    }    
}

class OrangeJuice extends Juice{

    public OrangeJuice(String name) {
        super(name);
    }
    public void sayName() {
        System.out.println("橙汁===: "+getName());
    }
}

class AppleJuice extends Juice{

    public AppleJuice(String name) {
        super(name);
    }
    public void sayName() {
        System.out.println("蘋果汁===: "+getName());
    }    
}

打印輸出

橙汁===: 果粒橙

? super T 限定了類型為T和T的父類型

        ArrayList<? super Juice> arrayList1 = new  ArrayList<Juice>();
        ArrayList<? super Juice> arrayList2 = new  ArrayList<Water>(); 
        //ArrayList<? super Juice> arrayList3 = new  ArrayList<OrangeJuice>();// 編譯報錯 OrangeJuice不是Juice也不是Juice的子類
        

這里很清楚的說明,我們限定了傳入類型只能是限定類型的或者其父類。

泛型super的讀和寫

對于 ArrayList<? super Juice> arrayList1

  • 無法確定讀出來的是Juice或者OrangeJuice或者AppleJuice。
  • 可以確定的是,讀出來肯定是一個Object(如果你非要強轉也行)。

.
.

  • 寫入的必須是T類型的或者T的子類型
    .
    .
    .

四.3、泛型限定extends和super的使用原則 PECS

PECE 原則: Producer Extends, Consumer Super

因為extends,可讀不可寫;super可寫不可讀

所以:
1、如果我們的操作基本上是只讀的,那么用extends
2、如果我們的操作基本上是只寫的,那么用super


** 小結:extends和super **

  • 阿敏說:extends

比如 ArrayList<? extends Juice> arrayList1,肯定不可以寫入,我們都不知道你放進來的是蘋果汁還是橙汁,然后別人到會來取得時候,已經限定了要一杯果汁,那我機器人這么笨怎么知道給一杯什么,所以你不要給我放進來了,因為我也無法給出去啊,多浪費啊,少年你自己喝了吧。
你這么笨機器人,你可以取,但是不不能存,沒有存哪有取,要你何用?
少年你這么說就不對了,你可以不要一個個添加嘛,你先把所有的符合我要求的數據批量準備好,比如 ArrayList<Juice> temp1 或者 ArrayList<Juice> temp2 ,數據你自己填充好,然后直接給我,這樣我給別人的時候也好給啊,我這么聰明的機器人一定不會弄錯啦。

ArrayList<Juice> tempList1 = new  ArrayList<Juice>(); 
        tempList1.add(new Juice("果汁1"));
        tempList1.add(new Juice("果汁2"));
        
        ArrayList<? extends Juice> juiceList1 = tempList1; 
        juiceList1.get(0).sayName();
        juiceList1.get(1).sayName();
        
        // =======
        ArrayList<OrangeJuice> tempList2 = new  ArrayList<OrangeJuice>();
        tempList2.add(new OrangeJuice("果粒橙1"));
        tempList2.add(new OrangeJuice("果粒橙2"));
        
        ArrayList<? extends Juice> juiceList2 = tempList2; 
        juiceList2.get(0).sayName();
        juiceList2.get(1).sayName();
        
輸出

果汁===: 果汁1
果汁===: 果汁2
橙汁===: 果粒橙1
橙汁===: 果粒橙2

.

  • 阿敏說:super存取疑惑
    .
    比如 ArrayList<? super Juice> arrayList2,可以寫入,為什么,你放進來的可以是果汁,可以是蘋果汁,可以是橙汁,都沒問題。但是取是萬萬不能的,里面存放辣么多不同的飲料,或者可能不同的飲料,你說要果汁,我是機器人那么笨,我拿什么給你。

參考:
JAVA泛型?通配符限定

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,321評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,559評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,442評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,835評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,581評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,922評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,931評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,096評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,639評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,374評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,591評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,104評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,789評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,196評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,524評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,322評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,554評論 2 379

推薦閱讀更多精彩內容

  • 我們知道,使用變量之前要定義,定義一個變量時必須要指明它的數據類型,什么樣的數據類型賦給什么樣的值。 假如我們現在...
    今晚打肉山閱讀 1,000評論 0 1
  • 前言 泛型(Generics)的型變是Java中比較難以理解和使用的部分,“神秘”的通配符,讓我看了幾遍《Java...
    珞澤珈群閱讀 7,899評論 12 51
  • 泛型的好處 使用泛型的好處我覺得有兩點:1:類型安全 2:減少類型強轉 下面通過一個例子說明: 假設有一個Tes...
    德彪閱讀 1,136評論 0 0
  • 引言:泛型一直是困擾自己的一個難題,但是泛型有時一個面試時老生常談的問題;今天作者就通過查閱相關資料簡單談談自己對...
    cp_insist閱讀 1,859評論 0 4
  • 哎,昨夜又失眠了,靜謐的夜晚,一個人,在陰暗角落里蜷著身子,靜靜的,癡癡的,癡癡的呆著。像這樣的夜晚不知經...
    芋頭西米露閱讀 369評論 0 1