Java泛型的学习
文章目錄
- 前言
- 泛型接口
- 定義實現類時指定泛型具體的數據類型
- 定義實現類時,泛型與接口相同
- 泛型接口的示例代碼
- 泛型類
- 演示代碼
- 泛型方法
- 演示代碼
- 泛型的通配符
- 泛型的區間限定
- 泛型應用
- 泛型性能
- 注意事項
前言
泛型的本質就是將數據類型參數化, 普通方法的參數值可以變,但是參數的數據類型是不能變的,那么需要使用不同數據類型參數時,我們通常的做法就是重載方法,而泛型則可以將數據類型也搞成跟參數的值一樣可以變的。
所以說泛型表示具體的數據類型未知,泛型其實就是一個參數變量,用來接收數據類型的名稱。當我們在定義接口、類、方法時,不知道使用什么數據類型的時候,可以使用泛型。
泛型參數名稱可以任何字符,甚至單詞,不過通常使用比較多了就是 E 和 T,E 表示 Element,T 表示 Type。
泛型分為泛型接口、泛型類和泛型方法。泛型接口、泛型類大家都比較熟悉,例如 List 就是泛型接口,而 ArrayList 就是泛型類, 我們經常看到 List<E> 的聲明,new ArrayList<E>() 的定義,尖括號里面的 E 就是泛型參數名稱,參數值就是你在創建對象的時候指定的具體數據類型,例如你創建集合對象的時候可以指定 E 是 String,或者 Integer,當然也可以指定自己定義的類(例如:CarBean)。在實際的工作中,泛型方法用的比較多,特別是解析自定義數據結構非常有用,類似于 toString 這種場景是百試不爽。
泛型接口
泛型接口中的泛型參數的值是在寫實現類的時候指定具體的值,或者在引用實現子類的示例對象時指定具體的值,但是引用實現子類對象時,實現子類也必須是泛型類才行。
定義實現類時指定泛型具體的數據類型
例如,定義一個泛型接口 Interator 和實現類 Scanner 的泛型實現如下:
public interface interator<E> {E next(); }Scanner 類實現了 Iterator 接口,并指定接口的泛型參數值為 String,所以重寫的 next() 方法中的泛型參數值就是 String:
public final class Scanner implements Iterator<String> {public String next(){} }定義實現類時,泛型與接口相同
在定義實現類時,接口使用什么泛型參數名,實現類就使用什么泛型參數名。其實就是定義一個泛型類,只不過這個泛型的泛型參數名稱需要和泛型接口的泛型參數名相同。創建對象的時候確定泛型的具體類型。
注:在創建對象時指定泛型具體的數據類型,那么具體的數據類型應該是保存在所創建的對象中,當訪問某個含有泛型參數的變量或者調用某個含有泛型參數的方法時,會把這個數據類型傳遞過去。
例如,List 接口和 ArrayList 實現類的泛型實現如下:
public interface List<E> {boolean add(E e);E get(int index); }pubic class ArrayList<E> implements List<E> {public boolean add(E e);public E get(int index); }泛型接口的示例代碼
泛型接口:
package priv.lwx.javaprac.genericinterface;/*** 泛型接口示例** @author liaowenxiong* @date 2021/11/22 15:02*/public interface GenericInterface<E> {void toString(E e); }泛型接口的第一種使用方式:定義實現類時指定泛型具體的數據類型
package priv.lwx.javaprac.genericinterface;/*** 泛型接口的第一種使用方式:* 定義實現類時指定泛型具體的數據類型** @author liaowenxiong* @date 2021/11/22 15:42*/public class GenericInterfaceImpl01 implements GenericInterface<Double>{@Overridepublic void toString(Double aDouble) {System.out.println(aDouble);} }泛型接口的第二種使用方式:在定義實現類時,接口使用什么泛型參數名,實現類就使用什么泛型參數名。其實就是定義一個泛型類,只不過這個泛型的泛型參數名稱需要和泛型接口的泛型參數名相同。創建對象的時候確定泛型的具體類型。
package priv.lwx.javaprac.genericinterface;/*** 泛型接口的第二種使用方式:* 在定義實現類時,接口使用什么泛型參數名,實現類就* 使用什么泛型參數名。其實就是定義一個泛型類,只不過這個泛型的泛型參數名稱需* 要和泛型接口的泛型參數名相同。創建對象的時候確定泛型的具體類型。** @author liaowenxiong* @date 2021/11/22 15:04*/public class GenericInterfaceImpl02<E> implements GenericInterface<E> {@Overridepublic void toString(E e) {System.out.println(e);} }測試類代碼:
package priv.lwx.javaprac.genericinterface;/*** 泛型接口測試** @author liaowenxiong* @date 2021/11/22 15:31*/public class GenericInterfaceDemo01 {public static void main(String[] args) {/*泛型接口的第一種使用方式測試:定義實現類時指定了具體的泛型類型,那么使用接口類型的變量接收實現類對象,在調用方法時會有風險*/GenericInterface gi1 = new GenericInterfaceImpl01();gi1.toString(123); // 參數類型是Object,編譯通過,運行時報錯:Integer cannot be cast to Double/*只能使用實現類變量接收對象才能避免風險,參數類型是確定的*/GenericInterfaceImpl01 gi2 = new GenericInterfaceImpl01();gi2.toString(23.99);/*泛型接口第二種使用方式測試:定義實現類時跟隨接口的泛型,那么使用接口類型的變量接收實現類對象時,接口指定什么數據類型,那么創建的對象中有關變量或方法的泛型參數就自動使用什么數據類型*/GenericInterface<String> gi3 = new GenericInterfaceImpl02<>();gi3.toString("liaowenxiong");GenericInterfaceImpl02<Double> gi4 = new GenericInterfaceImpl02<>();gi4.toString(2.9);} }泛型類
聲明定義泛型類,那么在創建泛型類對象時,指定具體的泛型參數值,即具體的數據類型。如果創建對象的時候不指定具體的數據類型,默認數據類型是 Object。
例如,ArrayList 類在聲明定義的時候不知道集合中會存儲什么類型的數據,所以類型使用泛型:
注:在創建對象時指定泛型具體的數據類型,那么具體的數據類型應該是保存在所創建的對象中,當訪問某個含有泛型參數的變量或者調用某個含有泛型參數的方法時,會把這個數據類型傳遞過去。
演示代碼
先聲明定義兩個 JavaBean 類型:
1.CarBean
public class CarBean {private String brand;private String name;private String price;public CarBean() {}public CarBean(String brand, String name, String price) {this.brand = brand;this.name = name;this.price = price;}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;CarBean carBean = (CarBean) o;return Objects.equals(brand, carBean.brand) &&Objects.equals(name, carBean.name) &&Objects.equals(price, carBean.price);}@Overridepublic int hashCode() {return Objects.hash(brand, name, price);}public String getBrand() {return brand;}public void setBrand(String brand) {this.brand = brand;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getPrice() {return price;}public void setPrice(String price) {this.price = price;} }2.HouseBean
public class HouseBean {private String brand;private String name;private String price;public HouseBean() {}public HouseBean(String brand, String name, String price) {this.brand = brand;this.name = name;this.price = price;}public String getBrand() {return brand;}public void setBrand(String brand) {this.brand = brand;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getPrice() {return price;}public void setPrice(String price) {this.price = price;}@Overridepublic String toString() {return "HouseBean{" +"brand='" + brand + '\'' +", name='" + name + '\'' +", prive='" + price + '\'' +'}';}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;HouseBean houseBean = (HouseBean) o;return brand.equals(houseBean.brand) &&name.equals(houseBean.name) &&price.equals(houseBean.price);}@Overridepublic int hashCode() {return Objects.hash(brand, name, price);} }再聲明定義一個泛型類:
public class GenericGoods<T> { // 尖括號表示聲明泛型參數名(或泛型變量名)為 Tprivate T goods;private String info_goods;public GenericGoods(T goods) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {this.goods = goods;this.info_goods = setData(goods);}public GenericGoods(T goods, String info_goods) {this.goods = goods;this.info_goods = info_goods;}/*** 聲明定義一個方法,將指定的goods對象中的所有成員變量的值取出來,并以“變量名=變量值”這樣的格式進行拼接后返回該字符處。* 這里使用泛型聲明參數類型,這樣就可以接收任意類型的參數,然后利用反射技術獲取對象所屬類型的變量名和變量值,再拼接成字符串返回*/private String setData(T goods) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {StringBuffer sb = new StringBuffer();String getterMethodName = "";String fieldName = "";Method mObject = null;String info_goods = "";// 獲取對象所屬類型的全部字段(字段是指類所聲明的靜態變量和成員變量)Field[] fields = goods.getClass().getDeclaredFields();if (fields.length > 0) {for (Field f : fields) {// 設置允許訪問私有域f.setAccessible(true);// 獲取字段名稱fieldName = f.getName();// 將字段名稱按符號“#”進行拼接sb.append(fieldName + "#");// 拼接getter方面名稱if (fieldName.length() > 1) {// 組裝getter方法名// substring(0,1)表示截取索引值從0到1的字符串,但是不含索引值為1的字符,其實就是截取第一個字符,并且轉換成大寫// substring(1)表示截取索引值為1后面的所有字符串,包含索引值為1的字符getterMethodName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);// 將拼接的getter方法名打印出來System.out.println(getterMethodName);} else {// 否則說明字段名稱僅有一個字符,直接轉換成大寫,再拼接 get 即可getterMethodName = "get" + fieldName.toUpperCase();// 將拼接的getter方法名打印出來System.out.println(getterMethodName);}// 獲取goods所屬類型中指定名稱的方法對象mObject = goods.getClass().getMethod(getterMethodName);// 獲取對象goods的成員變量的值,并且使用“變量名=變量值&”這樣的格式拼接起來// mObject.invoke(goods)是指調用對象goods的方法mObject,其實就是調用getter方法獲取相關成員變量的值info_goods += fieldName + "=" + mObject.invoke(goods) + "&";// 截取info_goods中索引值從0到info_goods最后一個索引值的子字符串,不包含最后一個索引值的字符,其實就是將最后一個字符“&”過濾掉info_goods = info_goods.substring(0, info_goods.length() - 1);}// 打印輸出所有字段名稱System.out.println(sb);}return info_goods;}public T getGoods() {return goods;}public void setGoods(T goods) {this.goods = goods;}public String getInfo_goods() {return info_goods;}public void setInfo_goods(String info_goods) {this.info_goods = info_goods;}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;GenericGoods<?> that = (GenericGoods<?>) o;return Objects.equals(goods, that.goods) &&Objects.equals(info_goods, that.info_goods);}@Overridepublic int hashCode() {return Objects.hash(goods, info_goods);} }main 方法測試:
public class Demo04Generic {public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {// 創建一個汽車產品CarBean cb = new CarBean();cb.setBrand("奔馳");cb.setName("S60");cb.setPrice("100000.00");// 創建一個通用商品對象,指定泛型參數具體的值,即指定具體的泛型類型,即指定具體的數據類型,那么創建的對象中有使用泛型參數的地方,具體的數據類型就已經明確了GenericGoods<CarBean> gg = new GenericGoods<>(cb); String info_goods = gg.getInfo_goods();System.out.println(info_goods);} }泛型方法
泛型方法的定義語法格式:修飾符 <泛型變量名> 返回值類型 方法名(泛型變量名 參數名) {方法體}。
調用方法的時候確定泛型參數的值(即具體的數據類型),即調用方法的時候,傳遞什么類型的參數,泛型就是什么類型。
演示代碼
public <T> String toString(T obj) { // 方法所聲明的泛型參數名和類的泛型參數名相同,不會造成沖突StringBuffer info_obj = new StringBuffer();StringBuffer fieldNames = new StringBuffer();Method m = null;String getMethodName = "";String fieldName = "";Field[] fields = obj.getClass().getDeclaredFields();System.out.println("對象所屬類型是:" + obj.getClass().getName());if (fields.length > 0) for (int i = 0; i < fields.length; i++) {fieldName = fields[i].getName();if (i == fields.length - 1) {fieldNames.append(fieldName);} else {fieldNames.append(fieldName + "#");}if (fieldName.length() == 1) {getMethodName = "get" + fieldName.toUpperCase();} else {getMethodName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);}try {m = obj.getClass().getMethod(getMethodName);if (i == fields.length - 1) {info_obj.append(fieldName + "=" + m.invoke(obj));} else {info_obj.append(fieldName + "=" + m.invoke(obj) + "&");}} catch (NoSuchMethodException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}}}return info_obj.toString();}main 方法測試代碼:
public class Demo05Generic {public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {HouseBean hb = new HouseBean();hb.setBrand("萬科");hb.setName("皇帝宮廷");hb.setPrice("200000.00");GenericGoods<CarBean> gg = new GenericGoods(); // 泛型參數指定了類型 CarBeanSystem.out.println(gg.toString(hb)); // 調方法的時候,依舊根據傳參類型來確定泛型參數的值System.out.println("=============================");CarBean cb = new CarBean();cb.setBrand("奔馳");cb.setName("S60");cb.setPrice("200000.00");System.out.println(gg.toString(cb));} }泛型的通配符
下面的例子,演示了如何使用泛型的通配符接收不同數據類型的 ArrayList 參數:
public class Demo02Generic {public static void main(String[] args) {ArrayList<Integer> list1 = new ArrayList<>();list1.add(15);list1.add(25);ArrayList<String> list2 = new ArrayList<>();list2.add("迪麗熱巴");list2.add("古力娜扎");printArrayList(list1);printArrayList(list2);}// 定義一個方法可以遍歷所有數據類型的 Arraylistpublic static void printArrayList(ArrayList<?> list) {//使用迭代器遍歷集合Iterator<?> it = list.iterator();while (it.hasNext()) {Object obj = it.next();System.out.println(obj);}} }泛型的區間限定
1.泛型的上限限定:? extends E 代表使用的泛型只能是類型 E 的子類或者本身
2.泛型的下限限定:? super E 代表使用的泛型只能是類型 E 的父類或者本身
下面的例子,就是演示了如何使用泛型的區間限定:
public class Demo03Generic {public static void main(String[] args) {Collection<Integer> list1 = new ArrayList<>();Collection<String> list2 = new ArrayList<>();Collection<Number> list3 = new ArrayList<>();Collection<Object> list4 = new ArrayList<>();getElement1(list1);getElement1(list2); // String 不是 Number 的子類,所以報錯getElement1(list3);getElement1(list4); // Object 不是 Number 的類,所以報錯getElement2(list1); // Integer 不是 Number 的父類,所以報錯getElement2(list2); // String 不是 Number 的父類,所以報錯getElement2(list3); getElement2(list4);}private static void getElement1(Collection<? extends Number> list1) {}private static void getElement2(Collection<? super Number> list1) {}// 定義一個方法可以遍歷所有數據類型的 Arraylistpublic static void printArrayList(ArrayList<?> list) {//使用迭代器遍歷集合Iterator<?> it = list.iterator();while (it.hasNext()) {Object obj = it.next();System.out.println(obj);}} }泛型應用
泛型性能
可以參考這篇文章: http://blog.csdn.net/hejiangtao/article/details/7173838
注意事項
1.泛型參數(或叫泛型變量)的值只能是引用類型,不能是基本數據類型
2.泛型參數(或叫泛型變量)可以有多個
總結
- 上一篇: 农村信用社是国企吗?
- 下一篇: 微信转账到别人银行卡可以撤销吗?