什麼是泛型
Java 泛型(generics)是 JDK 5 中引入的一個新特性, 泛型提供了編譯時類型安全檢測機制,該機制允許程序員在編譯時檢測到非法的類型。
泛型的本質是參數化類型,也就是說所操作的數據類型被指定爲一個參數。
廣泛的類型,類,接口和方法代碼可以應用於非常廣泛的類型,代碼與他們能夠操作的數據類型不再綁定在一起,同一套代碼,可以用於多種數據類型,這樣不僅可以複用代碼,降低耦合,同時可以提高代碼的可讀性和安全性.
使用場景:數據類型不確定
例如:現在要存儲一個學生的成績,但是成績有可能想存爲數字,小數,或者字符串(優秀,良好,好)之類的數據。這種數據都是類型不確定的。
可以使用Object來存儲該成績,但是這樣存儲的話會把所有類型都當做Object來對待。從而”丟失”了實際的數據類型。獲取數據的時候也需要轉換類型,效率低,還容易出錯。
一個簡單的泛型類
public class Pair<T> {
T first;
T second;
public Pair(T first, T second){
this.first = first;
this.second = second;
}
public T getFirst() {
return first;
}
public T getSecond() {
return second;
}
}
Pair類引入了類型變量T 用<>括起來,並放在類名之後.
類的屬性的類型也是T
T也是類方法的中返回類型
怎麼使用這個泛型類
用具體的類型代替類型變量就可以實例化泛型
Pair<Integer> minmax = new Pair<Integer>(1,100);
Integer min = minmax.getFirst();
Integer max = minmax.getSecond();
<Integer>表示傳入的類型是Integer
這個類型是可以變化的
Pair<String> kv = new Pair<String>("name",”nm");
泛型類可以有多個類型變量
public class Pair<U, V> {
U first;
V second;
public Pair(U first, V second){
this.first = first;
this.second = second;
}
public U getFirst() {
return first;
}
public V getSecond() {
return second;
}
}
使用
Pair<String,Integer> pair = new Pair<String,Integer>("name",100);
簡單來說,泛型類可以看作普通類的工廠
泛型類型的命名
泛型類型變量使用大寫形式,並且比較短 在java中變量E表示集合的元素類型,K和V分別表示表的關鍵字與值的類型. T(需要時還可以使用臨近的字母U和S)表示任意類型
注意:
Jdk1.7以後 構造函數中可以省略泛型類型 ArrayList<String> files = new ArrayList<>();
泛型不能使用在靜態屬性或者靜態方法上也不能用在全局常量上
泛型的原理
Java有Java編譯器和Java虛擬機,編譯器將Java源代碼轉換爲.class文件,虛擬機加載並運行.class文件。對於泛型類,Java編譯器會將泛型代碼轉換爲普通的非泛型代碼,就像上面的普通Pair類代碼及其使用代碼一樣,將類型參數T擦除,替換爲Object,插入必要的強制類型轉換。Java虛擬機實際執行的時候,它是不知道泛型這回事的,它只知道普通的類及代碼。
泛型的好處
既然泛型是實現原理是將泛型變爲了普通類和Object就可以的,而且泛型最後也變成了普通類,那麼爲什麼還是要使用泛型呢?
栗子
只使用Object,代碼寫錯的時候,開發環境和編譯器不能幫我們發現問題,看代碼:
Pair pair = new Pair("name",1);
Integer id = (Integer)pair.getFirst();
String name = (String)pair.getSecond();
寫代碼時,不小心,類型弄錯了,不過,代碼編譯時是沒有任何問題的,但,運行時,程序拋出了類型轉換異常ClassCastException。
但是如果使用泛型,那麼開發環境如Eclipse會提示你類型錯誤,即使沒有好的開發環境,編譯時,Java編譯器也會提示你。
1,類型安全。 泛型的主要目標是提高 Java 程序的類型安全。通過知道使用泛型定義的變量的類型限制,編譯器可以在一個高得多的程度上驗證類型假設。沒有泛型,這些假設就只存在於程序員的頭腦中(或者如果幸運的話,還存在於代碼註釋中)。
將運行時期的ClassCastException,轉移到了編譯時期變成了編譯失敗。
2,消除強制類型轉換。 泛型的一個附帶好處是,消除源代碼中的許多強制類型轉換。這使得代碼更加可讀,並且減少了出錯機會。提高了代碼的可讀性
泛型有三種使用方式,分別爲:泛型類、泛型接口、泛型方法
泛型方法
是不是泛型方法和他在不在泛型類中沒有關係
只有聲明瞭<T>的方法纔是泛型方法,泛型類中的使用了泛型的成員方法並不是泛型方法
public static <T> int indexOf(T[] arr, T elm){
for(int i=0; i<arr.length; i++){
if(arr[i].equals(elm)){
return i;
}
}
return -1;
}
類型參數在方法修飾符之後,返回類型之前
調用indexOf(new Integer[]{1,3,5}, 10)
或者indexOf(new String[]{"hello","老馬","編程"}, "老馬")
和泛型類一樣泛型方法也可以有多個參數
public static <U,V> Pair<U,V> makePair(U first, V second){
Pair<U,V> pair = new Pair<>(first, second);
return pair;
}
在大多數情況下,方法調用中可以省略<T>參數的,編譯器有足夠的信息推斷出所調用的方法
例如makePair(1,”a”);
類型參數能被用來聲明返回值類型,並且能作爲泛型方法得到的實際參數類型的佔位符。
類中的泛型方法轉載https://blog.csdn.net/s10461/article/details/53941091
public class GenericFruit {
class Fruit {
@Override
public String toString() {
return "fruit";
}
}
class Apple extends Fruit {
@Override
public String toString() {
return "apple";
}
}
class Person {
@Override
public String toString() {
return "Person";
}
}
class GenerateTest<T> {
public void show_1(T t) {
System.out.println(t.toString());
}
// 在泛型類中聲明瞭一個泛型方法,使用泛型E,這種泛型E可以爲任意類型。可以類型與T相同,也可以不同。
// 由於泛型方法在聲明的時候會聲明泛型<E>,因此即使在泛型類中並未聲明泛型,編譯器也能夠正確識別泛型方法中識別的泛型。
public <E> void show_3(E t) {
System.out.println(t.toString());
}
// 在泛型類中聲明瞭一個泛型方法,使用泛型T,注意這個T是一種全新的類型,可以與泛型類中聲明的T不是同一種類型。
public <T> void show_2(T t) {
System.out.println(t.toString());
}
}
public static void main(String[] args) {
Apple apple = new Apple();
Person person = new Person();
GenerateTest<Fruit> generateTest = new GenerateTest<Fruit>();
// apple是Fruit的子類,所以這裏可以
generateTest.show_1(apple);
// 編譯器會報錯,因爲泛型類型實參指定的是Fruit,而傳入的實參類是Person
// generateTest.show_1(person);
// 使用這兩個方法都可以成功
generateTest.show_2(apple);
generateTest.show_2(person);
// 使用這兩個方法也都可以成功
generateTest.show_3(apple);
generateTest.show_3(person);
}
}
什麼時候使用泛型方法
無論何時,如果你能做到,你就該儘量使用泛型方法。也就是說,如果使用泛型方法將整個類泛型化,那麼就應該使用泛型方法。另外對於一個static的方法而已,無法訪問泛型類型的參數。所以如果static方法要使用泛型能力,就必須使其成爲泛型方法。
泛型接口
public interface Comparable<T> {
public int compareTo(T o);
}
public interface Comparator<T> {
int compare(T o1, T o2);
boolean equals(Object obj);
}
和泛型類差不多
泛型接口的實現
public interface List <E>{
abstract boolean add(E e);
}
接口裏的常量不能是用泛型修飾,靜態方法也不能.
實現類,先實現接口,不理會泛型
public class ArrayList<E> implements List<E>{
}
調用者 : new ArrayList<String>() 後期創建集合對象的時候,指定數據類型
實現類,實現接口的同時,也指定了數據類型
public class XXX implements List<String>{
}
new XXX()
泛型的限定
java中用通配符和邊界來限制T的範圍
extends:可讀不可寫 限制父類 上限限制 可以傳遞T,傳遞他的子類對象
super :可寫不可讀 限制子類 下限限制 可以傳遞T,傳遞他的父類對象
都沒有 :沒有繼承很開心,一遇繼承就完蛋
java是單繼承,所有繼承的類構成一棵樹。
T<? super B>和T<? extends B>
假設A和B都在一顆繼承樹裏(否則super,extend這些詞沒意義)。
A super B 表示A是B的父類或者祖先,在B的上面。
A extend B 表示A是B的子類或者子孫,在B下面。
由於樹這個結構上下是不對稱的,所以這兩種表達區別很大。假設有兩個泛型寫在了函數定義裏,作爲函數形參(形參和實參有區別):
1) 參數寫成:T<? super B>,對於這個泛型,?代表容器裏的元素類型,由於只規定了元素必須是B的超類,導致元素沒有明確統一的“根”(除了Object這個必然的根),所以這個泛型你其實無法使用它,對吧,除了把元素強制轉成Object。所以,對把參數寫成這樣形態的函數,你函數體內,只能對這個泛型做插入操作,而無法讀
2) 參數寫成: T<? extends B>,由於指定了B爲所有元素的“根”,你任何時候都可以安全的用B來使用容器裏的元素,但是插入有問題,由於供奉B爲祖先的子樹有很多,不同子樹並不兼容,由於實參可能來自於任何一顆子樹,所以你的插入很可能破壞函數實參,所以,對這種寫法的形參,禁止做插入操作,只做讀取
鏈接:https://www.zhihu.com/question/20400700/answer/117624335
來源:知乎
?無限定通配符
所有的都可以傳,只能讀,不能寫
泛型的約束和侷限性
不能使用基本數據類型實例化類型參數
運行時類型信息不使用與泛型
不能創建參數化類型的數組
類型擦除可能會引發一些衝突
不能通過類型參數創建對象
不能用於靜態變量
(好多沒很好的理解先往後面學着應給能理解了吧)
好多來自https://www.cnblogs.com/swiftma/p/5847198.html