對於java的泛型我一直屬於只知其一;不知其二的,日常真心用的很少。直到閱讀《Effect Java》,看到不少日常不瞭解的用法,才下定決心,須要系統的學習,而且記錄下來。java
根據《Java編程思想》中的描述,泛型出現的動機:git
有不少緣由促成了泛型的出現,而最引人注意的一個緣由,就是爲了建立容器類。
複製代碼
泛型的思想很早就存在,如C++中的模板(Templates)。模板的精神:參數化類型github
JDK 1.5時增長了泛型,在很大的程度上方便在集合上的使用。編程
public static void main(String[] args) {
List list = new ArrayList();
list.add(11);
list.add("ssss");
for (int i = 0; i < list.size(); i++) {
System.out.println((String)list.get(i));
}
}
複製代碼
由於list類型是Object。因此int,String類型的數據都是能夠放入的,也是均可以取出的。可是上述的代碼,運行的時候就會拋出類型轉化異常,這個相信你們都能明白。數組
public static void main(String[] args) {
List<String> list = new ArrayList();
list.add("hahah");
list.add("ssss");
for (int i = 0; i < list.size(); i++) {
System.out.println((String)list.get(i));
}
}
複製代碼
在上述的實例中,咱們只能添加String類型的數據,不然編譯器會報錯。bash
泛型的三種使用方式:泛型類,泛型方法,泛型接口app
public class 類名 <泛型類型1,...> {
}
複製代碼
public <泛型類型> 返回類型 方法名(泛型類型 變量名) {
}
複製代碼
class Demo{
public <T> T fun(T t){ // 能夠接收任意類型的數據
return t ; // 直接把參數返回
}
};
public class GenericsDemo26{
public static void main(String args[]){
Demo d = new Demo() ; // 實例化Demo對象
String str = d.fun("湯姆") ; // 傳遞字符串
int i = d.fun(30) ; // 傳遞數字,自動裝箱
System.out.println(str) ; // 輸出內容
System.out.println(i) ; // 輸出內容
}
};
複製代碼
public interface 接口名<泛型類型> {
}
複製代碼
/**
* 泛型接口的定義格式: 修飾符 interface 接口名<數據類型> {}
*/
public interface Inter<T> {
public abstract void show(T t) ;
}
/**
* 子類是泛型類
*/
public class InterImpl<E> implements Inter<E> {
@Override
public void show(E t) {
System.out.println(t);
}
}
Inter<String> inter = new InterImpl<String>() ;
inter.show("hello") ;
複製代碼
//定義接口時指定了一個類型形參,該形參名爲E
public interface List<E> extends Collection<E> {
//在該接口裏,E能夠做爲類型使用
public E get(int index) {}
public void add(E e) {}
}
//定義類時指定了一個類型形參,該形參名爲E
public class ArrayList<E> extends AbstractList<E> implements List<E> {
//在該類裏,E能夠做爲類型使用
public void set(E e) {
.......................
}
}
複製代碼
父類派生子類的時候不能在包含類型形參,須要傳入具體的類型ide
public class A extends Container<K, V> {}
學習
public class A extends Container<Integer, String> {}
測試
public class A extends Container {}
public class Person {
public <T> Person(T t) {
System.out.println(t);
}
}
複製代碼
使用:
public static void main(String[] args) {
new Person(22);// 隱式
new <String> Person("hello");//顯示
}
複製代碼
特殊說明:
public class Person<E> {
public <T> Person(T t) {
System.out.println(t);
}
}
複製代碼
正確用法:
public static void main(String[] args) {
Person<String> person = new Person("sss");
}
複製代碼
PS:編譯器會提醒你怎麼作的
上界通配符顧名思義,<? extends T>表示的是類型的上界【包含自身】,所以通配的參數化類型多是T或T的子類。
它表示集合中的全部元素都是Animal類型或者其子類
List<? extends Animal>
複製代碼
這就是所謂的上限通配符,使用關鍵字extends來實現,實例化時,指定類型實參只能是extends後類型的子類或其自己。
//Cat是其子類
List<? extends Animal> list = new ArrayList<Cat>();
複製代碼
下界通配符<? super T>表示的是參數化類型是T的超類型(包含自身),層層至上,直至Object
它表示集合中的全部元素都是Cat類型或者其父類
List <? super Cat>
複製代碼
這就是所謂的下限通配符,使用關鍵字super來實現,實例化時,指定類型實參只能是extends後類型的子類或其自己
//Animal是其父類
List<? super Cat> list = new ArrayList<Animal>();
複製代碼
編譯器編譯帶類型說明的集合時會去掉類型信息
public class GenericTest {
public static void main(String[] args) {
new GenericTest().testType();
}
public void testType(){
ArrayList<Integer> collection1 = new ArrayList<Integer>();
ArrayList<String> collection2= new ArrayList<String>();
System.out.println(collection1.getClass()==collection2.getClass());
//二者class類型同樣,即字節碼一致
System.out.println(collection2.getClass().getName());
//class均爲java.util.ArrayList,並沒有實際類型參數信息
}
}
複製代碼
true
java.util.ArrayList
複製代碼
public class GenericTest {
public static void main(String[] args) throws Exception {
getParamType();
}
/*利用反射獲取方法參數的實際參數類型*/
public static void getParamType() throws NoSuchMethodException{
Method method = GenericTest.class.getMethod("applyMap",Map.class);
//獲取方法的泛型參數的類型
Type[] types = method.getGenericParameterTypes();
System.out.println(types[0]);
//參數化的類型
ParameterizedType pType = (ParameterizedType)types[0];
//原始類型
System.out.println(pType.getRawType());
//實際類型參數
System.out.println(pType.getActualTypeArguments()[0]);
System.out.println(pType.getActualTypeArguments()[1]);
}
/*供測試參數類型的方法*/
public static void applyMap(Map<Integer,String> map){
}
}
複製代碼
java.util.Map<java.lang.Integer, java.lang.String>
interface java.util.Map
class java.lang.Integer
class java.lang.String
複製代碼
public static void main(String[] args) throws Exception {
//定義一個包含int的鏈表
ArrayList<Integer> al = new ArrayList<Integer>();
al.add(1);
al.add(2);
//獲取鏈表的add方法,注意這裏是Object.class,若是寫int.class會拋出NoSuchMethodException異常
Method m = al.getClass().getMethod("add", Object.class);
//調用反射中的add方法加入一個string類型的元素,由於add方法的實際參數是Object
m.invoke(al, "hello");
System.out.println(al.get(2));
}
複製代碼
public class User<K, V> {
public void show(K k) { // 報錯信息:'show(K)' clashes with 'show(V)'; both methods have same erasure
}
public void show(V t) {
}
}
複製代碼
因爲泛型擦除,兩者本質上都是Obejct類型。方法是同樣的,因此編譯器會報錯。
換一個方式:
public class User<K, V> {
public void show(String k) {
}
public void show(V t) {
}
}
複製代碼
使用結果:
能夠正常的使用編譯器也不知道該建立那種類型的對象
public class User<K, V> {
private K key = new K(); // 報錯:Type parameter 'K' cannot be instantiated directly
}
複製代碼
靜態方法沒法訪問類上定義的泛型;若是靜態方法操做的類型不肯定,必需要將泛型定義在方法上。
若是靜態方法要使用泛型的話,必須將靜態方法定義成泛型方法。
public class User<T> {
//錯誤
private static T t;
//錯誤
public static T getT() {
return t;
}
//正確
public static <K> void test(K k) {
}
}
複製代碼
public class User<T> {
private T[] values;
public User(T[] values) {
//錯誤,不能實例化元素類型爲類型參數的數組
this.values = new T[5];
//正確,能夠將values 指向類型兼容的數組的引用
this.values = values;
}
}
複製代碼
泛型類不能擴展 Throwable,意味着不能建立泛型異常類 答案連接