造輪子的時候不敢用不會用泛型?那你看這篇就夠了!

DK_BurNIng發表於2020-04-04

閱讀本文解決什麼問題?

解決許多java開發 或者android開發 在平時寫一些基礎架構,或者是造一些輪子的時候不敢用泛型,用不好泛型的問題。 甚至有些人使用泛型的時候報錯都只會用idea提示的方法來修改程式碼,卻不知這樣改的原因,也不知道強轉泛型會有什麼惡果。

泛型用來解決什麼問題

先定義一個模仿List 的泛型list。 我們來看看這個乞丐版的list能幫我們做什麼事

public class CustomList<T> {
    Object[] array = new Object[0];

    public T get(int index) {
        return (T) array[index];
    }

    public void add(T instance) {
        array[array.length - 1] = instance;
    }
}
複製程式碼

看看怎麼使用他

 CustomList<String> customList = new CustomList<>();
        customList.add("hahahaha");
        String c = customList.get(0);
複製程式碼

到這,我們來看看 到底有啥好處。 首先看這個add方法,有了泛型以後,我們就不需要擔心型別轉換錯誤了。 因為我們在定義的時候 指定了泛型的型別,所以如果我們在呼叫add方法的時候傳了一個 非string型別的 那麼ide就會報錯了,即使你不用ide 用記事本寫,你編譯起來也會報錯的。 這就是靜態語言的好處了, 很多bug 在編譯的時候告訴你 不用像js 那麼蛋疼。

然後再看看get 這個函式,想一下 如果沒有泛型的話, 我們get出來的值 是一定要強轉成string才能賦值給c的, 但是現在有了泛型, 所以你可以直接get出來,這個型別轉換的東西 早就幫你做好了。

總結一下泛型的好處:

  1. 避免執行時出錯,編譯時就告訴你
  2. 方便你使用,省略強制型別轉換的程式碼

泛型為什麼不可以是靜態的?

造輪子的時候不敢用不會用泛型?那你看這篇就夠了!

這邊可以想一下,為什麼泛型不能用靜態的去定義?你怎麼改都是無法突破這個規則,是無法編譯成功的。

前面的例子我們可以知道,泛型主要用來可以初始化每個例項的。 注意是每個例項,他是動態確定的,

取決於你當時使用的時候 傳的是什麼引數,比如List<Object> List<String> List<Teacher>

對於一個靜態變數來說 你如果用泛型 那就會亂套了。 例如我們上面截圖的例子你用泛型會發生什麼?

static Object ins? static String ins? static Teacher ins? 大家都叫ins,那我怎麼知道 這個ins

到底應該是哪個型別的? 靜態變數 全域性唯一啊。 所以泛型是絕對不能用static來修飾的

這個地方一定要想明白了,想明白了,對你理解泛型是有好處的。

泛型的一種錯誤寫法

造輪子的時候不敢用不會用泛型?那你看這篇就夠了!

這種就是一種典型的錯誤寫法,明明介面中有泛型的,結果實體類中 把這個泛型給抹掉了。

雖然可以編譯通過,但是這種寫法就毫無意義了。

造輪子的時候不敢用不會用泛型?那你看這篇就夠了!

這種才是正確的寫法,和前面那種錯誤的寫法相比 我們明顯可以省略一次強制型別轉換。

大家可以比對一下這2種寫法 和 文章開頭泛型的2個優點。仔細體會一下。

如何正確extends泛型

造輪子的時候不敢用不會用泛型?那你看這篇就夠了!

泛型限制

interface IShop<T> {
    T buy(float price);
}


interface IPhoneShop2<T> extends IShop<T> {
    void repair(T phone);
}
複製程式碼

前面我們說道 ,泛型最大的好處就是方便使用,比如上面的程式碼 我們使用起來就很輕鬆如意,但是因為這樣的寫法 太隨意 所以要加一層限制。 上面的程式碼中,我們明明是一個手機商店,但實際使用的時候 卻可以隨便傳, 傳String 傳Apple 傳啥都行。 這和設計者的本意是不一致的。

所以泛型還可以加限制

interface Phone {

}

interface IPhoneShop2<T extends Phone> extends IShop<T> {
    void repair(T phone);
}

複製程式碼

這樣一來就可以限制我們使用時的型別,限制他一定得是Phone的型別才行。 考慮到java 是支援多介面,但是不支援多繼承的,泛型的限制也遵循這個規定。

造輪子的時候不敢用不會用泛型?那你看這篇就夠了!

泛型限制在list中的迷惑性

來講講泛型中一個令很多人想不通的地方

定義一個水果 然後有蘋果和香蕉

interface TFruit {

}

class Apple2 implements TFruit {
}

class Banana2 implements TFruit {
}
複製程式碼

然後我們看看他們的使用

造輪子的時候不敢用不會用泛型?那你看這篇就夠了!

報紅的地方為什麼報錯 是很多人想不明白的地方嗎,我們的apple 命名是fruit的子類啊,為啥報錯?

換個角度來思考一下這個問題:

造輪子的時候不敢用不會用泛型?那你看這篇就夠了!

所以對於 list 的 泛型來說, 他的型別 是動態確定的, 是沒辦法在編譯期 確定的, 所以你需要保證他 在= 兩邊 泛型都是絕對一致的 才能宣告成功,否則必定失敗

繼續看:

造輪子的時候不敢用不會用泛型?那你看這篇就夠了!

這個例子其實不難理解 為什麼add 方法不能編譯通過。

造輪子的時候不敢用不會用泛型?那你看這篇就夠了!

看到這,我相信很多人 都想告辭了。。。這tmd 泛型限制真多,咋用?不用了,以後自己造輪子自動遮蔽泛型了。

沒關係 耐心一下,我們再縷一遍。

// =左邊: 代表 我想要一個水果  =右邊 :我給你個蘋果  邏輯沒問題 編譯通過
        TFruit fruit = new Apple2();
        // =左邊: 代表 我想要一個水果的list 任意的水果 =右邊 :我給你個任意水果的list 邏輯沒問題 編譯通過
        List<TFruit> tf1 = new ArrayList<TFruit>();
        //既然是個水果的list 那我 add 蘋果香蕉 肯定沒問題
        tf1.add(new Apple2());
        tf1.add(new Banana2());


        // =左邊: 代表 我想要一個水果的list 任意的水果  =右邊 :我給你一個蘋果的list
        //這樣編譯肯定不通過,因為我想要的是水果的list 你卻給我一個蘋果的list 這樣你讓我就沒辦法玩了
        // 我想要水果 你只給我蘋果 那香蕉 葡萄 西瓜 我就沒辦法要了,所以你肯定不行 編譯不過
        List<TFruit> tf2 = new ArrayList<Apple2>();



        //=左邊: 代表 我想要一個list,這個list 必須是一個水果的型別,且只能是一種水果的型別   =右邊 :我給你一個蘋果的list
        //符合要求 編譯通過
        List<? extends TFruit> tf3 = new ArrayList<Apple2>();
        //我這個tf3 要求的是必須是一種水果的型別,但是我並不知道是那種型別,可能是水果 可能是葡萄 可能是香蕉
        // 所以你直接往我這塞一個確定好的水果  我肯定是不接受的,編譯肯定失敗
        tf3.add(new Apple2());
        tf3.add(new Banana2());
複製程式碼

?extends 好像有點蠢?

前面的文章看完,是不是覺得 這個?extends 有點蠢了,實際上 他在某種場景下 是 十分有用的(廢話 不然java為啥要這麼設計)

還是上面的水果,我們增加一個方法 返回對應水果的價格

interface TFruit {
    int getPrice();
}

class Apple2 implements TFruit {
    @Override
    public int getPrice() {
        return 1;
    }
}

class Banana2 implements TFruit {
    @Override
    public int getPrice() {
        return 2;
    }
}
複製程式碼

看看會有什麼問題

造輪子的時候不敢用不會用泛型?那你看這篇就夠了!

看這個函式的引數, 這個函式的引數 意思是 我想要一個list ,這個list裡面 可以放任何水果, 只要是水果就行,

但是在呼叫的時候 我們給他的 卻是蘋果的list 和 香蕉的list ,這就不是他想要的了,我想要任意型別的水果 你卻給我 蘋果或者是香蕉的,你幫我指定了具體型別 那肯定是不可以的。

所以這個時候 ? extends 就出場了

改完以後 就直接編譯成功了:

造輪子的時候不敢用不會用泛型?那你看這篇就夠了!

回想一下上一小節的內容,這個? extends 不就是代表 想要任意一種水果嗎

你既然想要的是任意 一種水果,那我給你蘋果或者香蕉 肯定是ok的。

你們使用的泛型埋坑了嗎?

造輪子的時候不敢用不會用泛型?那你看這篇就夠了!

而且是執行期間報錯了。後果比較嚴重,不易察覺。

一個香蕉當然不能轉成蘋果。

平時寫程式碼的時候一定不要這麼寫。

?super 又是啥,怎麼用。

造輪子的時候不敢用不會用泛型?那你看這篇就夠了!

改成

造輪子的時候不敢用不會用泛型?那你看這篇就夠了!

就可以, 這裡怎麼理解?

加上? super就代表 等號右邊的東西 你只要可以接受一個蘋果的list就可以了。

造輪子的時候不敢用不會用泛型?那你看這篇就夠了!

造輪子的時候不敢用不會用泛型?那你看這篇就夠了!

造輪子的時候不敢用不會用泛型?那你看這篇就夠了!

相關文章