閱讀本文解決什麼問題?
解決許多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出來,這個型別轉換的東西 早就幫你做好了。
總結一下泛型的好處:
- 避免執行時出錯,編譯時就告訴你
- 方便你使用,省略強制型別轉換的程式碼
泛型為什麼不可以是靜態的?
這邊可以想一下,為什麼泛型不能用靜態的去定義?你怎麼改都是無法突破這個規則,是無法編譯成功的。
前面的例子我們可以知道,泛型主要用來可以初始化每個例項的。 注意是每個例項,他是動態確定的,
取決於你當時使用的時候 傳的是什麼引數,比如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就可以了。