為什麼要有泛型泛型出現的背景
集合容器在在編譯階段不能確定容器儲存的元素實際上是什麼型別的物件,在JDK1.5之前只能把元素設計成java.lang.Object,JDK1.5以後使用泛型來解決。因為編譯期除了元素的型別不確定,但是其他部分是確定的,例如如何增刪改查元素。因此把元素的型別設計成一個引數,這個引數就是泛型(Generic)。
泛型在Java的集合框架中廣泛使用
java.util.Collection<E>就是一個泛型介面java.util.ArrayList<E>就是一個泛型類java.util.Collections類中的public static <T> boolean addAll(Collection<? super T> c, T... elements)就是一個泛型方法,其中T是泛型型別。E和T表示一種未知的資料型別,在使用時確定具體的資料型別(只能是引用資料型別)泛型的概念泛型就是允許在定義類、介面、方法時透過一個標識標識類中某個屬性的型別,或者某個方法的返回值以及引數型別,這個型別引數將在使用時確定其具體的資料型別。
針對泛型介面,實現類在實現泛型介面時會確定泛型介面的具體資料型別,或者實現類在實現泛型介面時不確定具體資料型別,而是等到建立實現類物件時確定泛型介面的具體資料型別針對泛型類,在建立泛型的類建立物件時,需要指定泛型類的具體資料型別。針對泛型方法,在呼叫方法時傳入的實參或者返回值指定具體資料型別。泛型的好處泛型的作用就是在編譯器編譯時限定資料型別,如果資料型別不一致會出現編譯錯誤。當編譯後泛型型別在位元組碼檔案會被擦除。沒有泛型之前集合中儲存的元素在編譯期間都是java.lang.Object型別,因此可以存放任意資料型別,沒有編譯期資料型別檢查,而在執行期間需要進行型別轉換後再使用,否則會出現型別轉換異常。
而集合使用了泛型以後,在編譯期間就可以檢測並限定資料型別,如果集合中的資料型別不一致,那麼就會造成程式編譯錯誤。而且因為泛型限制集合的元素型別,在使用時也不再需要使用強制型別轉換。
java.util.Map<K,V> 也是一個泛型介面,該介面需要兩個泛型引數,代表Map的Key和Value,在使用Map時需要指定Key和Value的型別。JDK7以後的泛型支援自動型別推斷,例如這裡在使用HashMap實現類建立Map介面的物件時,new HashMap的<>不需要再指定泛型的具體型別,此時編譯器會自動推斷為=左邊的泛型型別。
/** * 泛型在Map集合的使用 * */ @Test public void testMapGeneric(){ //JDK7以後泛型支援型別推斷,即HashMap的<>不用再指定泛型的具體型別 Map<String,Integer> info =new HashMap<>(); info.put("tony",28); info.put("jack",32); Set<Map.Entry<String, Integer>> entries = info.entrySet(); for (Map.Entry<String, Integer> entry : entries) { System.out.println(entry); } }
自定義泛型泛型類的定義和使用
泛型類定義的語法格式
public class 類名<泛型變數>{}
例如 public class ArrayList<E>,E就是泛型變數名,表示Element也就是元素的意思。
泛型類的應用場景是當該類中的某個方法的形參型別不知道是什麼型別或者方法的形參型別和返回型別一致但是也不確定是什麼型別時,此時就可以定義泛型類。
在使用含有泛型的類建立物件時,需要指定泛型類的具體資料型別。
如果定義了泛型類,那麼就要在例項化時指定泛型的實際型別。
/** * 自定義泛型類的使用 */ @Test public void testGenericOrder(){ //如果定義了泛型類,例項化物件時沒有指明類的泛型,則認為此泛型類為Object型別 Order order=new Order(); order.setT(""); //使用泛型類:在使用含有泛型的類建立物件時需要指定具體的資料型別 例如這裡的String Order<String> orderInstance=new Order<>(00001L,"上海市","雙11訂單"); orderInstance.setT("雙12訂單"); System.out.println(orderInstance); }
程式執行結果
一般情況下子類繼承了泛型父類,要麼在繼承時指定父類泛型的具體型別:PingDuoDuoOrder在繼承泛型類Order類時指定了泛型的實際型別,此時PingDuoDuoOrder不再是泛型類
@Test public void testGenericPinDuoDuoOrder(){ //由於子類在繼承帶泛型的父類時已經指定類泛型的具體型別為String,因此在例項化子類物件時無需再指定泛型的型別 PinDuoDuoOrder pinDuoDuoOrder =new PinDuoDuoOrder(); pinDuoDuoOrder.setT("拼多多訂單PDD0000000001"); System.out.println(pinDuoDuoOrder); }
程式執行結果
要麼時子類在繼承父類時不指定泛型型別,此時子類型別也是一個泛型型別:TaoBaoOrder繼承自泛型類Order並沒有指定泛型類的實際型別,因此還是一個泛型類
@Test public void testGenericTaoBaoOrder(){ //由於TaoBaoOrder在繼承Order時沒有指定泛型的型別,因此在例項化物件時需要指定具體的泛型型別 TaoBaoOrder<String> taoBaoOrder=new TaoBaoOrder<>(); taoBaoOrder.setT("淘寶訂單TB0000000001"); System.out.println(taoBaoOrder); }
程式執行結果
泛型的使用注意事項
在定義和使用泛型時,泛型的引數可以有多個,例如Map<K,V> 就有兩個兩個引數,當有多個引數時,引數之間使用逗號分隔泛型的構造器如下 Order(){},而不是錯誤的Order<T>(){}; 但是例項化後,操作原來泛型位置的結構必須與指定的泛型型別一致,例如Order<String> orderInstance=new Order<>(00001L,"上海市","雙11訂單");泛型不同的引用型別不能賦值,因為泛型是編譯期的型別檢查,編譯器會將泛型不同的引用當作不同的資料型別,但是執行時只有一個ArrayList載入到JVM中。 /** * 泛型不同的引用不能賦值 */ @Test public void GenericDiffReferenceAssignment(){ ArrayList<String> stringList=null; ArrayList<Integer> integerList=null; //此處會編譯錯誤,泛型不同的引用不能賦值 //stringList=integerList; }
泛型如果不指定,將被擦除,泛型對應的類按照Object處理,但是不等價Object,因此泛型定以後就必須使用,如果不用就不定義。如果泛型定義在介面或者是抽象類中,不能建立泛型類的物件JDK7以後針對泛型提供了型別推斷的特性,例如Map<String,Integer> data = new HashMap<>();,賦值號右邊的<> 不用再寫泛型的引數型別,以賦值號左邊的為準泛型的具體資料型別不能使用基本資料型別,如果想要使用基本資料型別,可以使用它們對應的包裝類替換。泛型類或者泛型介面中宣告的泛型,在本類或者介面中代表某種型別,可以作為非靜態成員變數的型別,非成員方法的引數、返回值,但是在靜態方法中不能使用泛型型別異常類不能是泛型的,try/catch中也不能使用泛型型別不能使用new E[],但是可以使用 E[]elements =new Object[capacity],例如ArrayList原始碼中宣告的是 Obejct[] elementData,而非泛型引數型別的陣列 public Order(){ //編譯錯誤 //T[] array=new T[10]; //編譯透過 T[] array=( T[])new Object[10]; }
子類在繼承父類或者實現介面時除了指定或者保留父類的泛型,還可以增加自己的泛型泛型介面的定義和使用
泛型介面的定義語法格式
public interface 介面名<泛型變數>{}
泛型介面體系結構
自定義泛型介面 BaseDao,它是一個數據訪問物件父類介面,包含一些操作資料庫表的通用的方法。
只有在定義方法時在返回值前面定義泛型變數才是泛型方法。
泛型方法的定義格式
修飾符 <泛型變數> 返回值型別 方法名(形參列表){}
定義一個 有引數,無返回值的泛型方法genericMethodWithoutReturnValue()
/** * 定義一個泛型方法 * 方法的返回值是void * 方法的引數型別是泛型 * @param <T> 泛型引數 */ public <T> void genericMethodWithoutReturnValue(T type){ //判斷是否為Employee型別 if(type instanceof Employee){ Employee employee= (Employee) type; System.out.println("獲取員工資訊"+employee); } else{ System.out.println(type); } }
呼叫有參,無返回值的泛型方法
定義有參有返回值的泛型方法,該方法用於將任意型別陣列複製給集合,泛型方法可以是靜態的,泛型引數是在呼叫方法時確定的,並非例項化類時確定的。
/** * 定義有參有返回值的泛型方法 * * 實現泛型陣列複製到泛型集合 * @param elements 泛型陣列型別 * @param <T> 泛型型別 * @return List<T> */ public static <T> List<T> copyFormArrayToList(T[]elements){ List<T> result=new ArrayList<>(); for (T element : elements) { result.add(element); } return result; }
呼叫有參有返回值的泛型方法
泛型和繼承在編譯期,Object和String存在著父子類關係,Object[]和String[]也是父子類關係關係,因此可以將String型別的變數值賦值給Object型別的變數,也可以將String[]的變數值賦值給Object[]的變數。但是List<Object>和List<String>沒有父子類關係,而是並列的關係,因此不能將List<String>型別的變數值賦值給List<Object>型別的變數。
/** * 雖然Object是String的父類 * 但是List<Object>和List<String>不具有父子類關係,而是並列關係 */ @Test public void testGenericInheritance(){ Object obj=new Employee(); String str="HelloWorld"; obj=str; System.out.println(obj); Object[] objects=new Employee[3]; String[] strings={"跟光磊學Java開發","跟光磊學前端開發","跟光磊學大資料開發"}; objects=strings; System.out.println(Arrays.toString(objects)); List<Object> objectList=new ArrayList<>(); List<String> stringList=new ArrayList<>(); //編譯錯誤,objectList和stringList型別不一致 沒有父子類關係 //objectList=stringList; }
程式執行結果
而List和ArrayList是父子類關係,類實現介面在某種程度上等價於繼承,因此List<String>和ArrayList<String>在編譯期是同一個型別,因此可以將ArrayList<String>的變數值賦值給List<String>型別的變數
/** * List和ArrayList是父子類關係 * List<String>和ArrayList<String>是同一個型別 */ @Test public void testGenericInterfaceImp(){ List<String> stringList=new ArrayList<>(); ArrayList<String> stringArrayList=new ArrayList<>(); stringList=stringArrayList; }
泛型萬用字元
由於List<String>和List<Object> 不是同一種類型,而且還可能新增其他的型別,因此無法統一處理,但是它們的共同父類是List<?>。在不知道使用什麼具體資料型別來表示泛型的具體資料型別,此時可以使用?,?表示未知萬用字元。
例如這裡定義一個printList方法用於列印輸出List集合元素的內容
/** * 列印集合的元素 * 泛型不知道使用什麼型別來確定泛型的具體資料型別時可以使用? * ? 表示泛型萬用字元 * @param list 因為使用了泛型萬用字元? list的元素可以是任意型別 */ public void printList(List<?> list){ System.out.println(list); }
在呼叫方法時,可以傳遞任意元素型別的List
/** * 泛型萬用字元的使用 * */ @Test public void testGenericWildcard(){ List<String> subjectList=new ArrayList<>(); subjectList.add("跟光磊學java開發"); subjectList.add("跟光磊學前端開發"); subjectList.add("跟光磊學大資料開發"); printList(subjectList); List<Integer> data=new ArrayList<>(); data.add(15000); data.add(12000); data.add(20000); printList(data); }
程式執行結果
對於使用了泛型萬用字元(?)的資料結構,在使用是不能新增除了null以外的資料,在讀取資料時,資料的型別預設是Object型別。
阿里巴巴泰山版java開發手冊中指出泛型萬用字元的上限<? extends T>來接收返回的資料,此寫法的泛型集合不能使用add()方法,而泛型萬用字元的下限<? super T>不能使用get()方法,兩者在介面呼叫賦值的場景容易出錯。萬用字元的上限<? extends T>適用於頻繁往外讀取內容,萬用字元的下限<? super T>適合往裡插入。
/** * 萬用字元的讀寫要求 * 對於使用了泛型萬用字元的資料結構,不能再新增資料,但是可以獲取資料,資料的型別預設是Object */ @Test public void testGenericWildcardReadWrite(){ List<String> subjectList=new ArrayList<>(); subjectList.add("跟光磊學java開發"); subjectList.add("跟光磊學前端開發"); subjectList.add("跟光磊學大資料開發"); List<?> list=subjectList; //對於使用了泛型萬用字元,List<?>就不能新增除了null以外的資料,因為所有引用資料型別的初始值是null,但是沒什麼意義 list.add(null); //使用泛型萬用字元,獲取元素的型別是Object Object object = list.get(0); String subject= (String) object; System.out.println(subject); }
程式執行結果
泛型萬用字元的高階用法
泛型萬用字元的上限<? extends T> 表示未知型別必須是T型別或者T的子類定義獲取訂單資訊方法,該方法的引數是(List<? extends Order<String>>,也就是List中的 元素必須是Order或者Order中的子類
/** * 獲取訂單資訊 * @param orderList List的元素必須是Order或者是Order的子類 */ public void getOrderInfo(List<? extends Order<String>> orderList){ for (Order<String> order : orderList) { System.out.println("獲取訂單資訊"+order); } }
呼叫獲取訂單資訊方法,此時引數只能是Order或者是Order的子類,例如這裡的PingDuoDuoOrder
/** * 泛型萬用字元的上限測試用例 */ @Test public void testGenericWildCardUpper(){ List<Order<String>> orderList = new ArrayList<>(); Order<String> order1=new Order<>(00001L,"上海市","雙11訂單"); Order<String> order2=new Order<>(00002L,"北京市","雙11訂單"); Order<String> order3=new Order<>(00003L,"深圳市","雙11訂單"); orderList.add(order1); orderList.add(order2); orderList.add(order3); getOrderInfo(orderList); List<PinDuoDuoOrder> pingDuoDuoOrderList = new ArrayList<>(); PinDuoDuoOrder pingDuoDuoOrder1=new PinDuoDuoOrder(00001L,"上海市","拼多多訂單"); PinDuoDuoOrder pingDuoDuoOrder2=new PinDuoDuoOrder(00002L,"北京市","拼多多訂單"); PinDuoDuoOrder pingDuoDuoOrder3=new PinDuoDuoOrder(00003L,"深圳市","拼多多訂單"); pingDuoDuoOrderList.add(pingDuoDuoOrder1); pingDuoDuoOrderList.add(pingDuoDuoOrder2); pingDuoDuoOrderList.add(pingDuoDuoOrder3); getOrderInfo(pingDuoDuoOrderList); }
程式執行結果
泛型萬用字元的下限<? super T> 表示未知型別必須是T型別或者T的父類定義printData()方列印集合的元素到終端,方法的引數是(List<? super Integer> list,意味著可以傳Integer以及它的父類,例如java.lang.Number,java.lang.Object
/** * 列印集合元素內容 * @param list 此時的list元素的內容可以是Integer或者是Integer的父類,例如NUmber,Object */ public void printData(List<? super Integer> list){ for (Object object:list){ System.out.println("遍歷接元素內容"+object); } System.out.println("******楚河******漢界******"); }
呼叫printData()方法
/** * 泛型萬用字元的上限測試用例 */ @Test public void testGenericWildCardLower(){ List<Integer> integerList=new ArrayList<>(); integerList.add(15000); integerList.add(12000); integerList.add(20000); printData(integerList); List<Number> numberList=new ArrayList<>(); numberList.add(100_0000); numberList.add(200_0000); numberList.add(300_0000); printData(numberList); List<Object> subjectList=new ArrayList<>(); subjectList.add("跟光磊學java開發"); subjectList.add("跟光磊學前端開發"); subjectList.add("跟光磊學大資料開發"); printData(subjectList); }
程式執行結果