泛型,其實算是Java當中比較難的語法了,很多人一開始都對其一知半解,也很害怕閱讀帶泛型的原始碼,雖然看起來語法很難,但當你理解后會覺得很簡單,其實只是一個紙老虎罷了,下面,我將會用非常簡單易懂的方式帶你去理解它,相信你在認真看完后會有非常大的識訓,從此不會再畏懼它!
Java之泛型
- 一. 泛型的定義
- 二. 為什么要用到泛型
- 三. 泛型的寫法
- 四. 泛型的使用實體
- 1. 求最大值
- 2. 優化
- 五. 通配符
- 1. 基本寫法
- 2. 上界
- 3. 下界
- 六. 泛型的限制
一. 泛型的定義
這里大家可以不必去看網上的有些定義,因為相對于比較學識訓,只需記住泛型可以在程式設計中指定某種型別,讓程式的設計更加規范化即可
二. 為什么要用到泛型
了解到了泛型是什么后,那我們來討論討論為什么要用泛型這個語法,這個語法到底是干什么的?別急,下面,我先給大家舉一個例子:
class Stack {
public Object[] objects;
public int top;
public Stack() {
this.objects =new Object[10];
}
public void push(Object obj) {
objects[this.top++] = obj;
}
public Object get() {
return objects[this.top-1];
}
}
大家可以看看這是在干什么呢?這是我們自己寫了一個堆疊,然后將堆疊里的陣列型別設定成Object型別,這樣的話這個堆疊里任意型別的資料都可以存放了(Object類是任何類的父類,不管插入什么型別的資料,都可以發生向上轉型)
下面,我們來測驗一下
public class Test {
public static void main(String[] args) {
Stack stack=new Stack();
stack.push(1);
stack.push(2);
stack.push("123");
String str=(String)stack.get();
}
}
可以看到,我們可以向自己寫的堆疊里放入整形以及字串等等任何型別的資料,但注意一下取出資料的時候要進行強制型別轉換
以上這樣寫,可以向堆疊里存放任何型別的資料,比較通用,其優點也可以變成缺點,正因為太通用了,使代碼的規范性降低,看起來比較凌亂,這時候,我們可以考慮使用泛型,這樣可以在類中或者Java集合中存放特定的資料(使用Java集合時,一般都要用到泛型,而自定義的型別中可以使用泛型也可以不使用)
三. 泛型的寫法
以自定義的型別為例,寫法為在類名后面加上尖括號,里面寫上一個字母(注意,此處寫任何字母都可以,只起到一個標記這個類為泛型類的作用)
class Stack<T>
而在new物件時,以堆疊里只能存放整形為例,前面的尖括號必須寫基本資料型別對應的包裝類,而后面的尖括號可以不用寫,示例如下:
Stack<Integer> stack = new Stack<>();
補一下Java中的基本資料型別與對應的包裝類:
因此,我們前面寫的自定義的堆疊可以寫成以下形式(以存放整形為例):
class Stack<T> {
public T[] objects;
public int top;
public Stack() {
this.objects = (T[])new Object[10];
}
public void push(T obj) {
objects[this.top++] = obj;
}
public T get() {
return objects[this.top-1];
}
}
Stack<Integer> stack = new Stack<>();
stack.push(1);
stack.push(2);
int ret = stack.get();
System.out.println(ret);
特別注意此處:public Stack() { this.objects = (T[])new Object[10]; }
這里不能寫成this.objects=new T[10];
原因:
1. 不能new泛型型別的陣列
2. 也可理解為泛型是先檢查后編譯的,如果new泛型型別的陣列的話,編譯器檢查時并不知道T是什么型別的,因此會報錯,而編譯的時候才會進行擦除機制,都會將其轉換為Object型別
3. 正因為有這個擦除機制,這里才能進行陣列整體強制型別轉換(一般陣列不能整體進行強制型別轉換),因為泛型只是在編譯的時候起作用,而實際運行時都會被擦除成Object型別,即實際運行時是沒有泛型這個概念的,也即實際運行時型別都是一樣的,所以T本質上是object型別的,所以此代碼等價于不進行強制型別轉換!!!
4.而直接指定泛型的代碼(不是T) 比如:Stack<Integer>和Stack<Character>都是在運行時直接把尖括號里的型別擦掉了,可以看到直接列印的結果(并沒有列印出型別):


此處注意多理解理解

四. 泛型的使用實體
1. 求最大值
以上就是泛型的一個重要知識點了,但光看是不夠的,還是得通過例子讓大家有一個更為深入的理解,比如,如何寫一個泛型類來求陣列的最大值呢?
基本的框架大概是這樣的:(沒看懂的小可愛好好看看上面講的內容哦)
class Algorithm<T extends Comparable<T>> {
public T findMax(T[] array) {
T max = array[0];
for (int i = 1; i < array.length; i++) {
if(max < array[i]) {
max = array[i];
}
}
return max;
}
}
但是此代碼if(max < array[i])會報錯,為什么呢?因為將來給T傳的值一定是一個參考型別,參考型別不能直接比較大于或者小于的,是要用Comparable或Comparator介面里的方法比較的,因為泛型在編譯的時候會被擦除成Object型別,但Object類本身并沒有實作Comparable或Comparator介面,所以我們要控制其不要擦除到Object類,所以要給泛型指定一個邊界
具體寫法如下:
class Algorithm<T extends Comparable<T>> {
public T findMax(T[] array) {
T max = array[0];
for (int i = 1; i < array.length; i++) {
//max < array[i]
if(max.compareTo(array[i]) < 0) {
max = array[i];
}
}
return max;
}
}
class Algorithm<T extends Comparable<T>>
注意,extends叫做上界,此代碼代表的意思為T這個泛型類會擦除到實作了Comparable介面的地方,換句話說,這個T型別一定是實作了Comparable介面的
同理:這個代碼public class MyArrayList<E extends Number> { ... }代表E為Number的子類或Number本身
下面讓我們來用一下:
Algorithm<Integer> algorithm1 = new Algorithm<>();
Integer[] integers = {1,2,13,4,5};
Integer ret = algorithm1.findMax(integers);
System.out.println(ret);
運行結果如下:

成功了!
2. 優化
經過上面的努力,我們已經寫出了一個泛型類來求一個陣列的最大值了,但是,上面的例子是一個整形陣列,那么我們能不能在陣列里存放別的型別去比較呢?答案是可以的,但是我們還得去new一個物件,例如:Algorithm<String> algorithm2 = new Algorithm<>();這樣很麻煩,但是我們可以將求最大值的方法設定成靜態的class Algorithm2 <T>,因為是靜態的方法,不需要new物件,所以就沒有在new物件時指定泛型的程序了,所以沒必要給方法后加尖括號,但是去掉之后,代碼又會被錯:
我們可以這樣修改:
class Algorithm2 {
public static<T extends Comparable<T>> T findMax(T[] array) {
T max = array[0];
for (int i = 1; i < array.length; i++) {
if(max.compareTo(array[i]) < 0) {
max = array[i];
}
}
return max;
}
}
此方法public static<T extends Comparable<T>> T findMax(T[] array){}叫做泛型方法
下面繼續帶大家來用一下:
public static void main(String[] args) {
Integer[] integers = {1,2,13,4,5};
//會根據形參的型別推匯出整個泛型的型別引數
Integer ret = Algorithm2.findMax(integers);
System.out.println(ret);
Integer ret2 = Algorithm2.<Integer>findMax(integers);
System.out.println(ret2);
}
注意,ret1寫法和ret2寫法是一樣的,都可以
列印結果如下:

五. 通配符
1. 基本寫法
通配符也是泛型的一種,下面我們來寫一個泛型方法來列印集合中的元素
class Test {
public static<T> void print(ArrayList<T> list) {
for (T t : list) {
System.out.println(t);
}
}
這個寫法很簡單,上文都講過了,那么讓我們來試著用一下吧:
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
Test.print(list);
}
列印的結果如下:

除了以上這種寫法,我們還可以將其改成通配符的寫法,先給大家上代碼:
//?代表通配符 擦除機制 Object
public static void print2(ArrayList<?> list) {
for (Object t : list) {
System.out.println(t);
}
}
}
此處for (Object t : list)必須這樣寫,因為通配符也是有擦除機制的,會在編譯器編程Object型別,
2. 上界
- 語法:<? extends 上界>
- 示例:
public static void printAll(MyArrayList<? extends Number> list) {
...
}
代表可以傳入型別實參是 Number 子類的任意型別的 MyArrayList
所以以下呼叫都是正確的:
printAll(new MyArrayList<Integer>());
printAll(new MyArrayList<Double>());
printAll(new MyArrayList<Number>());
以下呼叫都是錯誤的:
printAll(new MyArrayList<String>());
printAll(new MyArrayList<Object>());
3. 下界
下界和上界的用法很類似
- 語法:<? super 下界>
- 示例:
public static void printAll(MyArrayList<? super Integer> list) {
...
}
代表可以傳入型別實參是 Integer 父類的任意型別的 MyArrayList
所以以下呼叫是正確的:
printAll(new MyArrayList<Integer>());
printAll(new MyArrayList<Number>());
printAll(new MyArrayList<Object>());
以下呼叫是錯誤的:
printAll(new MyArrayList<String>());
printAll(new MyArrayList<Double>());
六. 泛型的限制
學習完后,我們應該注意泛型使用程序中以下一些限制:
- 泛型型別引數不支持基本資料型別
- 無法實體化泛型型別的物件
- 無法使用泛型型別宣告靜態的屬性
- 無法使用 instanceof 判斷帶型別引數的泛型型別(因為被擦除機制擦除了)
- 無法創建泛型類陣列
- 無法 create、catch、throw 一個泛型類例外(例外不支持泛型)
- 泛型型別不是形參一部分,無法多載

好啦,本次泛型知識點的分享就先告一段落了,整理不易,但如果能幫到大家很開心了,也希望大家多理解理解,不論是剛開始學習還是復習,都值得仔細揣摩哦!一起加油吧!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/298651.html
標籤:java
下一篇:??高級JAVA開發必備技能??java8 新日期時間API((三)JSR-310:格式化和決議),2萬字詳解(JAVA 小虛竹,建議收藏)

