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

閱讀本文解決什麼問題?

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

泛型用來解決什麼問題

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

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

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

public void add(T instance) {
array[array.length - 1] = instance;
}
}
/<code>

看看怎麼使用他

<code> CustomList<string> customList = new CustomList<>();
customList.add("hahahaha");
String c = customList.get(0);/<string>/<code>

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

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

總結一下泛型的好處:

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

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

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


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

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

取決於你當時使用的時候 傳的是什麼參數,比如List<object> List<string> List<teacher>/<string>/<object>

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

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

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

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

泛型的一種錯誤寫法

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


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

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

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


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

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

如何正確extends泛型

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


泛型限制

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


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

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

所以泛型還可以加限制

<code>interface Phone {

}

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

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


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


泛型限制在list中的迷惑性

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

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

<code>interface TFruit {

}

class Apple2 implements TFruit {
}


class Banana2 implements TFruit {
}/<code>

然後我們看看他們的使用


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


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

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


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


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

繼續看:

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


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


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


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

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

<code>// =左邊: 代表 我想要一個水果  =右邊 :我給你個蘋果  邏輯沒問題 編譯通過
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());/<apple2>/<apple2>/<tfruit>/<tfruit>/<tfruit>/<code>

?extends 好像有點蠢?

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

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

<code>interface TFruit {
int getPrice();
}

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

class Banana2 implements TFruit {
@Override
public int getPrice() {
return 2;

}
}/<code>

看看會有什麼問題

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

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

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

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

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


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


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

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

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


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


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

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

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

?super 又是啥,怎麼用。

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


改成


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


就可以, 這裡怎麼理解?

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

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


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

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


作者:DK_BurNIng
鏈接:https://juejin.im/post/5e86a58d6fb9a03c8b4bf701


分享到:


相關文章: