夯實(shí)JAVA基礎(chǔ)之 - 泛型

泛型的定義及使用

1. 定義泛型:
Student<T>
2. 類中使用泛型
Student<T> 這個(gè) T 表示派生自object的任何類,比如 string、 intenger、 Double 等。 但是要注意的是, T 一定要
是派生自 object 類。

Class Student<T> {
    private T name ;

    public T getName(){
        return name;
    }

    public void setName(T name){
        this.name = name;
    }
}
3. 使用泛型類
// 構(gòu)造實(shí)例
Student<String> s = new Student<String>();
s.setName("張三");
system.out.printin(s.getName());

4. 使用泛型的優(yōu)勢(shì)?

I.  為什么不用 object 代替泛型?就是因?yàn)閺?qiáng)制轉(zhuǎn)換會(huì)出現(xiàn)意想不到的錯(cuò)誤。
II. 在編譯期就能報(bào)錯(cuò)。

多泛型變量的定義及字母規(guī)范

1. 多泛型變量定義
上面我們只定義了一個(gè)泛型變量,那如果需要多個(gè)變量怎么辦?  只需要類似下面這樣就行了。
新加的變量和上例中的 T 用法一樣, 這樣的變量想加幾個(gè)加幾個(gè)。只要用 "," 隔開就行。

class Student<T,E>{
    Private T name;
    Private E age;
    ......
}

class Student<T,E,U,K,V>{}

2. 字母規(guī)范

指定泛型的可以是任意一個(gè)大寫字母,咩有特定的含義。 但是為了提高可讀性,大家還是用比較有意義的字母為好。

  • E, Element 常用在 collection 中,如: List<E>, iterator<E>, Set<E>
  • K,V key-value 鍵值對(duì)
  • N, Number 數(shù)字
  • T, Type 類型,如 string integer 等

泛型接口的定義和使用

  • 非泛型類
  • 泛型類

泛型函數(shù)的定義和使用

不管是靜態(tài)還是非靜態(tài)函數(shù),不管是有沒有返回值,都在返回值類型前加符號(hào)<T> ,以用來(lái)標(biāo)識(shí)泛型。

  • 靜態(tài)泛型函數(shù)
  • 非靜態(tài)泛型函數(shù)
public class StaticFans {
    //靜態(tài)函數(shù)
    public static <T> void StaticMethod(T a){
        Log.d("harvic","StaticMethod: "+a.toString());
    }
    //普通函數(shù)
    public <T> void OtherMethod(T a){
        Log.d("harvic","OtherMethod: "+a.toString());
    }
    // 有返回值的泛型函數(shù)
    public static <T> List<T> parseArray(String response,Class<T> object){
        List<T> modelList = JSON.parseArray(response, object);
        return modelList;
    }

}

其他用法:Class<T> 類傳遞及泛型數(shù)組

Class<T> 其實(shí)也是一種泛型,用來(lái)裝載 class 對(duì)象的。

public final class Class<T> implements Serializable {  
   …………  
}  

泛型高級(jí)知識(shí): 類型綁定和通配符

泛型的類型綁定: extends

  1. <T extends BoundingType> 就是給泛型參數(shù)加一個(gè)界限。
  2. 此時(shí)的 extends 不等同于 繼承。和繼承沒有任何聯(lián)系。
  3. BoundingType 可以是類,也可以是接口。 T 代表的類型是被包含的意思。具有 BoundingType的功能。
  4. 能提前調(diào)用 T 的父類或父接口中的方法。
  • 綁定接口
    public interface Comparable<T> {
        boolean compareto(T i);
    }
    
    public class StringCompare implements Comparable<StringCompare> {
        String mStr;
        public StringCompare(String string) {
            this.mStr = string;
        }
        
        @Override
        public boolean compareto(StringCompare i) {
            if (mStr == null) {
                throw new NullPointerException();
            }
            if (mStr.length() > i.mStr.length()) {
                return true;
            }
            return false;
        }
    }
    
    public static <T extends Comparable> T min(T... a) {
        T smallest = a[0];
        for (T item : a) {
            if (smallest.compareto(item)) {
                smallest = item;
            }
        }
    }
    
    // 調(diào)用時(shí)候
    // 可以看出類型綁定有兩個(gè)作用:
    // 1、對(duì)填充的泛型加以限定 
    // 2、使用泛型變量T時(shí),可以使用BoundingType內(nèi)部的函數(shù)。
    
    public static <T extends Comparable> T min(T... a) {
        T smallest = a[0];
        for (T item : a) {
            if (smallest.compareto(item)) {
                smallest = item;
            }
        }
        return smallest;
    }
    
    StringCompare result = min(new StringCompare("1"),new StringCompare("123"));
    Log.e("xyd","result = " + result.mStr);
       
    
  • 綁定類
    class Fruit{
        private String name;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    
    public static <T extends Fruit> String getFruitName(T t){
        return t.getName();
    }
    
    class Banana extends Fruit{
        public Banana() {
            setName("banana");
        }
    }
    
    class Apple extends Fruit{
        public Apple() {
            setName("apple");
        }
    }
    // 最后使用;
    Log.e("xyd","FruitName = " + getFruitName(new Banana()));
    Log.e("xyd","FruitName = " + getFruitName(new Apple()));
    
  • 綁定多個(gè)綁定,用 & 連接
    public static <T extends Fruit&Serializable> String getFruitName(T t){
        ...
    }
    加深難度,如果有多個(gè)泛型,每個(gè)泛型都帶綁定?
    public static <T extends Comparable & Serializable, U extends Runnable> T foo(T a, U b){
        ...
    }
    

通配符

無(wú)邊界通配符
  • 無(wú)邊界通配符初識(shí)
    有沒有辦法,只生成一個(gè)變量,可以將不同的實(shí)例賦值給它呢?
    
    首先定義定義一個(gè)泛型類:
    class Point<T>{
        private T x;
        private T y;
    
        public Point() {
        }
    
        public Point(T x, T y) {
            this.x = x;
            this.y = y;
        }
    
        public T getX() {
            return x;
        }
    
        public void setX(T x) {
            this.x = x;
        }
    
        public T getY() {
            return y;
        }
    
        public void setY(T y) {
            this.y = y;
        }
    }
    
    Point<?> point;
    point = new Point<Integer>(3,3);
    point = new Point<Float>(2.3f,5.6f);
    point = new Point<Double>(6.3d,46.5d);
    point = new Point<Long>(10l,58l);
    這里的 ? 就是無(wú)邊界通配符。就是一個(gè)任意的類,一個(gè)未知的符號(hào)。
    point = new Point<String>("","");
    point = new Point<Object>();
    不光能將 T 填充為數(shù)值類,任意的 Object 的子類都可以匹配給通配符。
    
  • 通配符 ? 和 T 的區(qū)別
    他們倆沒有任何聯(lián)系;
    泛型變量T不能在代碼中用于創(chuàng)建變量,只能在類、接口、函數(shù)中聲明后使用。
    無(wú)界通配符只能用于填充泛型變量T,表示通配任何類型。它是用來(lái)填充 T 的,只是填充方式的一種??!
    
    // 無(wú)邊界通配符填充
    Box<?> box;
    // 其他類型填充
    Box<String> stringBox;
    
    
  • 通配符只能用于填充泛型變量T,不能用于定義變量
    只能用來(lái)填充變量T的位置,不能用于定義變量。通配符的使用位置只有:
    Box<?> box;
    box = new Box<String>();
    
    而不能用于定義變量:
    box = new Box<?>(); // 錯(cuò)誤
    ? x;                // 錯(cuò)誤
    

通配符 ? 的 extends 綁定 // 上邊界限定通配符

通配符?可以代表任意類型,但跟泛型一樣,如果不加以限定,在后期的使用中編譯器可能不會(huì)報(bào)錯(cuò)。所以我們同樣要對(duì)通配符加以限定。
綁定的形式,同樣是通過extends 關(guān)鍵字,意義和使用方法都和泛型變量一致。
為了保證泛型類有意義,需要對(duì)泛型類的參數(shù)做限制,不能超出限制之外。
如有的參數(shù)只有是數(shù)字類型才有意義。

Point<? extends Number> point;
point = new Point<Number>();
point = new Point<Float>(3.4f,4.3f);
point = new Point<Double>(3.4d,4.3d);
point = new Point<Long>(3.4l,4.3l);
point = new Point<String>("","");       //  報(bào)錯(cuò)
point = new Point<Object>();            //  報(bào)錯(cuò)

//----
編譯時(shí)候,最后兩行會(huì)報(bào)錯(cuò)。
new Point<Number>(); 不會(huì)報(bào)錯(cuò),說(shuō)明無(wú)邊界通配符的extends綁定包括邊界自身。
無(wú)邊界通配符只是泛型T的填充方式,給他加上限定,只是限定了賦值給它的實(shí)例類型。
如果想從根本上解決亂填充Point的問題,需要從 Point 泛型類定義時(shí)候就加上 <T extends Number>

注意: 利用<? extends Number> 定義的變量,只可取其中的值,不可修改。

Point<? extends Number> point;
point = new Point<Integer>(3,33);
Number Integer_x = point.getX();
point.setX(new Integer(222));       // 報(bào)錯(cuò)

// 為什么會(huì)報(bào)錯(cuò)???
因?yàn)?Point 的類型是由 Point<? extends Number> 決定的,并不會(huì)因?yàn)?point = new Point<Integer>(3,33)而改變類型。
即便 point = new Point<Integer>(3,3) 之后, point 的類型依然是 Point<? extends Number>(),
即派生自 Number 的未知類型?。?! 
怎么理解? 如果在 point = new Point<Integer>(3,3) 之后, point 就變成了 Poin<Integer> 類,那后面
point = new Point<Long>(2l,22l); 肯定會(huì)因?yàn)轭愋筒黄ヅ涠鴪?bào)編譯錯(cuò)誤了。正因?yàn)?point 的類型始終是 
Point<? extends Number> 因此能繼續(xù)被各種類型的實(shí)例賦值。

繼續(xù)正題,為什么會(huì)報(bào)錯(cuò)?
point 的類型為 Point<? extends Number> , 那就是說(shuō)填充 point 泛型變量 T 的是 <? extends Number>,
這是一個(gè)什么類型? 未知類型。怎么可能用一個(gè)未知類型來(lái)給設(shè)置內(nèi)部值! 這是不合理的。
但是取值時(shí), 正由于泛型變量T被填充為<? extends Number> 所以編譯器能確定 T 肯定是 Number 的子類。編譯器就會(huì)用 Number 來(lái)填充 T。

也就是說(shuō),編譯器,只要能確定通配符類型,就會(huì)允許,如果無(wú)法確定通配符的類型,就會(huì)報(bào)錯(cuò)。

通配符 ?的 supper 綁定 // 下邊界限定通配符

總結(jié):
通配符的使用可以對(duì)泛型參數(shù)做出某些限制,使的代碼更安全,對(duì)于上邊界和下邊界限定的通配符總結(jié)如下:

  • 使用 List<? extends C> list 這種形式,表示 list 可以引用一個(gè) ArrayList ( 或者其它 List 的 子類 ) 的對(duì)象,這個(gè)對(duì)象包含的元素類型是 C 的子類型 ( 包含 C 本身)的一種。
  • 使用 List<? super C> list 這種形式,表示 list 可以引用一個(gè) ArrayList ( 或者其它 List 的 子類 ) 的對(duì)象,這個(gè)對(duì)象包含的元素就類型是 C 的超類型 ( 包含 C 本身 ) 的一種。

參考資料:
Java 泛型總結(jié)(一):基本用法與類型擦除
Java 泛型總結(jié)(二):泛型與數(shù)組
Java 泛型總結(jié)(三):通配符的使用
夯實(shí)JAVA基本之一 —— 泛型詳解(1):基本使用
夯實(shí)JAVA基本之一——泛型詳解(2):高級(jí)進(jìn)階

什么是協(xié)變?為什么數(shù)組是支持協(xié)變的?為什么泛型不支持協(xié)變?
參考知乎 - 胖胖 的答案

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 引言:泛型一直是困擾自己的一個(gè)難題,但是泛型有時(shí)一個(gè)面試時(shí)老生常談的問題;今天作者就通過查閱相關(guān)資料簡(jiǎn)單談?wù)勛约簩?duì)...
    cp_insist閱讀 1,939評(píng)論 0 4
  • 一、基本數(shù)據(jù)類型 注釋 單行注釋:// 區(qū)域注釋:/* */ 文檔注釋:/** */ 數(shù)值 對(duì)于byte類型而言...
    龍貓小爺閱讀 4,454評(píng)論 0 16
  • 開發(fā)人員在使用泛型的時(shí)候,很容易根據(jù)自己的直覺而犯一些錯(cuò)誤。比如一個(gè)方法如果接收List作為形式參數(shù),那么如果嘗試...
    時(shí)待吾閱讀 1,125評(píng)論 0 3
  • 幾日燈火竟嶙峋,肅氣彌彌貫天宇。北風(fēng)嗚咽無(wú)所依,空對(duì)歸人唱金縷。我言塊壘堪入酒,以是堪堪了心期。信有杯酒銜荒月,自...
    酒客背寒南山死閱讀 387評(píng)論 0 1
  • 無(wú)禮儀不聊天。雖然說(shuō)隔著屏幕看不見對(duì)方的表情,但不代表在QQ和微信聊天可以不注重禮儀了。左愛可是見過斗表情包都能吵...
    請(qǐng)叫我四爺閱讀 2,346評(píng)論 0 3

友情鏈接更多精彩內(nèi)容