關於JAVA泛型那點事
相逢便是緣,路過點個贊 ^.^
概述
Java 泛型(generics)是 JDK 5 中引入的一個新特性,在面向物件程式設計及各種設計模式中有非常廣泛的應用。泛型提供了編譯時型別安全檢測機制,該機制允許程式設計師在編譯時檢測到非法的型別。泛型的本質是引數化型別,也就是說所操作的資料型別被指定為一個引數。
注意引數的型別只能是引用型別,不能是原始型別(int,char,double 這種),據說是當初設計師偷懶!!!
根據慣例,型別引數是單個大寫字母,該字母用於指示所定義的引數型別。下面列出每個用例的標準型別引數:
- T 型別
- E 元素
- K 鍵
- V 值
- N 數字
- S、U、V 等:多引數情況中的第 2、3、4 個型別
泛型類別
泛型類
泛型類的宣告和非泛型類的宣告類似,除了在類名後面添加了型別引數宣告部分,型別可以有多個:。最典型的就是各種容器類,如:List,Set,Map
List<String> strList = new ArrayList<>();
List<Integer> intList = new ArrayList<>();
Map<String, Object> map = new HashMap<>();
複製程式碼
舉個例子
通常我們在開發中會定義一個統一的介面返回型別,根據傳入的引數型別 T 不同可以返回不同的資料型別,其中 T 可以用其他標識代替,如K,V,U
public class HttpResult<T> {
private T data;
public HttpResult(T data) {
this.data = data;
}
public T getData() {
return data;
}
public void setDatastatic main(String[] args) {
HttpResult<String> strResult = new HttpResult<>("String型別");
HttpResult<Boolean> booleanResult = new HttpResult<>(true);
HttpResult<Integer> integerResult = new HttpResult<>(100);
System.out.println("str:" + strResult.getData());
System.out.println("boolean:" + booleanResult.getData());
System.out.println("integer:" + integerResult.getData());
}
}
複製程式碼
原始類 (raw class)
如果我們使用一個泛型類,但是不帶型別引數,那麼我們使用的是原始類。
List list = new ArrayList();
list.add(new Object());
list.add(99);
list.add("123");
複製程式碼
泛型方法
- 泛型方法,和泛型類相似,是在呼叫方法的時候指明泛型的具體型別 。
- 所有泛型方法宣告都有一個型別引數宣告部分(由尖括號分隔: )在方法返回型別之前。
- static 方法和static 域均不可以引用類的型別變數,因為存在泛型擦除,後面講
- 只有宣告瞭型別引數的方法才是泛型方法,泛型類中使用了泛型的方法不是泛型方法
擴充套件上面的例子
private int code;
private String msg;
/**
* 具體返回資料,定義為泛型
* T 可以為其他任意標識 E,K,V等
*/
private T data;
/**
* 這個不是泛型方法,雖然用到了 T
* 但是返回型別前面沒有使用 型別宣告
*/
(int code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
/**
* 這個是泛型方法
* 返回型別前面有型別宣告<T>,T 可以為其他標識
* !注意 這裡的型別宣告和類的型別申明都是 <T>,但這2個宣告是不相關的,
* static 方法和static 域均不可以引用類的型別變數,應為存在泛型擦除。
* 為了防止混淆,這裡可以使用其他標識D代替:
* public static <D> HttpResult<D> success(D data){
* return new HttpResult<>(200, "success", data);
* }
*/
static <T> HttpResult<T> successreturn 200, "success", data);
}
複製程式碼
泛型介面
和定義泛型類相似。
interface GenericService<T getOwn();
}
複製程式碼
介面實現未傳入具體型別的,仍然以泛型的形式實現
GenericServiceImpl<T> implements T> {
/**
* 仍然顯示泛型
*/
@Override
returnSelf(T oldEntity) {
System.out.println(oldEntity.getClass());
return oldEntity;
}
}
複製程式碼
介面實現傳入具體型別的,顯示具體型別
Integer> Integer> {
/**
* 這裡用傳入的Integer型別替換掉原來的泛型
*/
public Integer (Integer oldEntity) {
System.out.println(oldEntity.getClass());
return oldEntity;
}
}
複製程式碼
萬用字元
定義基本關係
// 基本樹形結構
BaseTree {}
// 部門樹
DepartmentTree extends // 選單樹繼承基本樹結構
MenuTree BaseTree {}
複製程式碼
無界萬用字元 ?
問號 (
?
) 萬用字元可用於使用泛型程式碼表示未知型別。萬用字元可用於引數、欄位、區域性變數和返回型別。但最好不要在返回型別中使用萬用字元,因為確切知道方法返回的型別更安全。
假設我們想編寫一個方法來驗證指定的List
中是否存在指定的物件。我們希望該方法接受兩個引數:一個是未知型別的 List
,另一個是任意型別的物件。
checkList(List<?> myList, T obj){
if(myList.contains(obj)){
System.out.println("The list contains the element: " + obj);
} else {
System.out.println("The list does not contain the element: " + obj);
}
}
複製程式碼
上限萬用字元
限制型別為特定型別或者特定型別的子類
當獲取資料是會隱式的轉為其基類(或者Object基類)
/**
* 只能傳 BaseTree 或者它的子類 DepartmentTree、 menuTree
*
* @param tree 傳入的樹結構
*/
static <T extends BaseTree> UpperBoundType(T tree) {
if (tree instanceof DepartmentTree) {
System.out.println("depTree");
} else instanceof MenuTree) {
System.out.println("menuTree");
} else {
System.out.println("baseTree");
}
return tree;
}
複製程式碼
/**
* 指定型別 T 返回型別 T
*/
UpperBoundList(List<T> treeList) {
T tree = treeList.get(0);
/**
* 使用 <? extends BaseTree>
* 獲取列表元素時只能使用父類去接受,
* 但還是可以獲取到子類的型別
*/
static BaseTree (List<? extends BaseTree> treeList) {
// 這裡只能使用BaseTree去接收引數,要用子型別需要強轉
BaseTree tree = treeList.get(0);
// 傳入DepartmentTree 或者 MenuTree時,下面的依然可以執行
"depTree");
DepartmentTree base = (DepartmentTree) tree;
} "menuTree");
MenuTree ment = (MenuTree) tree;
} "baseTree");
BaseTree base = (BaseTree) tree;
}
return tree;
}
複製程式碼
多介面限制
同時繼承類並實現多個介面的可以用 & 連線
MultiExtend<T Number & Comparable<T> & Cloneable> {
}
複製程式碼
下限萬用字元
限制型別為特定型別或者特定型別的超類
/**
* 只能傳MenuTree或者它的父類的List
* 接收型別為Object
* 需要強轉到對應的型別
*/
LowerBoundType(List<? super MenuTree> menuTree) {
Object obj = menuTree.get(if (obj "menuTree");
MenuTree ment = (MenuTree) obj;
} instanceof BaseTree) {
System.out.println("baseTree");
BaseTree base = (BaseTree) obj;
}
}
複製程式碼
泛型擦除
泛型類可以由編譯器通過所謂的型別擦除過程而轉變成非泛型類。這樣,編譯器就生成了一種與泛型類同名的原始類,但是型別引數都被刪除了。對應的型別變數由他們的型別界限來代替,上限萬用字元的為其父類,下限萬用字元的為Object。
泛型的限制
基本型別
基本型別(int,double)不能用做型別引數,必須使用包裝類(Integer,Double)
static 語境
在一個泛型類中,static方法和static域都不可以引用類的型別變數,因為類在型別擦除後就不存在型別變量了。
泛型類的例項化
不能建立一個泛型型別的例項化,下面程式碼是錯誤的:
T obj = new T();
複製程式碼
在型別擦除後T由它的限界代替,可能是Object(甚至是抽象類),因此對new沒有呼叫意義。
泛型陣列
同樣不能建立一個泛型陣列,下面程式碼是錯誤的:
T [] arr = new T(10);
複製程式碼
在型別擦除後T由它的限界代替,很可能是Object T,於是對T[]的型別轉換將無法進行,應為Object[] IS-NOT-A T[]。
一般不建議建立泛型陣列,儘量使用ArrayList來代替泛型陣列。下面提供了一種建立泛型陣列的方法
GenericArrayFactory<private T[] array;
/**
* 初始化陣列
*
* @param componentType 陣列的類別
* @param length 容量
*/
init(Class<T> componentType, int length) {
// 這裡會有一個型別轉換警告 Object to T[]
//noinspection unchecked
array = (T[]) Array.newInstance(componentType, length);
}
/**
* 賦值
*
* @param index 索引
* @param item 值
*/
putint index, T item) {
array[index] = item;
}
/**
* 獲取陣列
*
* @return arr
*/
public T[] getArray() {
return array;
}
(String[] args) {
GenericArrayFactory<Integer> genericArrayFactory = new GenericArrayFactory<>();
// 初始化Integer型別的陣列
genericArrayFactory.init(Integer.class, 3);
genericArrayFactory.put(0,250); word-wrap: inherit !important; word-break: inherit !important;" class="hljs-number">9);
genericArrayFactory.put(1,250); word-wrap: inherit !important; word-break: inherit !important;" class="hljs-number">8);
genericArrayFactory.put(2,250); word-wrap: inherit !important; word-break: inherit !important;" class="hljs-number">7);
Integer[] intArray = genericArrayFactory.getArray();
for (Integer integer : intArray) {
System.out.println(integer);
}
}
}
複製程式碼