Java Genrics 是 Java 5 中引入的最重要的功能之一。javascript
若是您一直在使用Java Collections並使用版本 5 或更高版本,那麼我肯定您已經使用過它。java
Java 中具備集合類的泛型很是容易,可是它提供了比僅建立集合類型更多的功能。程序員
咱們將在本文中嘗試學習泛型的功能。若是咱們使用專業術語,對泛型的理解有時會變得混亂,所以,我將盡可能保持其簡單易懂。面試
Java 5 中添加了泛型,以提供編譯時類型檢查,並消除了ClassCastException
使用集合類時常見的風險。整個收集框架都進行了重寫,以使用泛型進行類型安全。讓咱們看看泛型如何幫助咱們安全地使用集合類。算法
List list = new ArrayList(); list.add("abc"); list.add(new Integer(5)); for(Object obj : list){ String str=(String) obj; }
上面的代碼能夠很好地編譯,可是在運行時會引起ClassCastException,由於咱們試圖將列表中的對象強制轉換爲String,而其中一個元素是Integer類型。在Java 5以後,咱們使用以下收集類。編程
List<String> list1 = new ArrayList<String>(); // java 7 ? List<String> list1 = new ArrayList<>(); list1.add("abc"); //list1.add(new Integer(5)); //編譯錯誤 for(String str : list1){ //no type casting needed, avoids ClassCastException }
請注意,在建立列表時,咱們已指定列表中元素的類型爲String。所以,若是咱們嘗試在列表中添加任何其餘類型的對象,則該程序將引起編譯時錯誤。還要注意,在循環中中,咱們不須要列表中元素的類型轉換,所以在運行時刪除了ClassCastException。安全
咱們可使用泛型類型定義本身的類。泛型類型是經過類型進行參數化的類或接口。咱們使用尖括號(<>)來指定類型參數。markdown
爲了瞭解其好處,咱們假設有一個簡單的類:框架
package com.journaldev.generics; public class GenericsTypeOld { private Object t; public Object get() { return t; } public void set(Object t) { this.t = t; } public static void main(String args[]){ GenericsTypeOld type = new GenericsTypeOld(); type.set("Pankaj"); String str = (String) type.get(); //type casting, error prone and can cause ClassCastException } }
請注意,在使用此類時,咱們必須使用類型轉換,而且它能夠在運行時產生ClassCastException。如今,咱們將使用Java通用類替換以下所示的相同類。async
package com.journaldev.generics; public class GenericsType<T> { private T t; public T get(){ return this.t; } public void set(T t1){ this.t=t1; } public static void main(String args[]){ GenericsType<String> type = new GenericsType<>(); type.set("Pankaj"); //valid GenericsType type1 = new GenericsType(); //raw type type1.set("Pankaj"); //valid type1.set(10); //valid and autoboxing support } }
注意main方法中GenericsType類的使用。咱們不須要進行類型轉換,而且能夠在運行時刪除ClassCastException。若是咱們在建立時未提供類型,則編譯器將發出警告,「 GenericsType是原始類型。
泛型類型GenericsType
Object
,所以它容許String和Integer對象。可是,咱們應始終嘗試避免這種狀況,由於在處理可能產生運行時錯誤的原始類型時,咱們必須使用類型轉換。
還要注意,它支持Java自動裝箱。
Comparable接口是接口中泛型的一個很好的例子,它寫爲:
package java.lang; import java.util.*; public interface Comparable<T> { public int compareTo(T o); }
以相似的方式,咱們能夠在Java中建立通用接口。咱們也能夠像Map界面具備多個類型參數。一樣,咱們也能夠爲參數化類型提供參數化值,例如new HashMap<String, List<String>>();
有效。
Java通用類型命名約定能夠幫助咱們輕鬆理解代碼,而且具備命名約定是Java編程語言的最佳實踐之一。所以,泛型也帶有本身的命名約定。一般,類型參數名稱是單個大寫字母,以能夠實現與Java變量區分開。最經常使用的類型參數名稱爲:
有時咱們不但願整個類都被參數化,在這種狀況下,咱們能夠建立java泛型方法。因爲構造函數是一種特殊的方法,所以咱們也能夠在構造函數中使用泛型類型。
這是一個顯示Java泛型方法示例的類。
package com.journaldev.generics; public class GenericsMethods { //Java Generic Method public static <T> boolean isEqual(GenericsType<T> g1, GenericsType<T> g2){ return g1.get().equals(g2.get()); } public static void main(String args[]){ GenericsType<String> g1 = new GenericsType<>(); g1.set("Pankaj"); GenericsType<String> g2 = new GenericsType<>(); g2.set("Pankaj"); boolean isEqual = GenericsMethods.<String>isEqual(g1, g2); //above statement can be written simply as isEqual = GenericsMethods.isEqual(g1, g2); //This feature, known as type inference, allows you to invoke a generic method as an ordinary method, without specifying a type between angle brackets. //Compiler will infer the type that is needed } }
注意_的isEqual_方法簽名顯示了在方法中使用泛型類型的語法。另外,請注意如何在咱們的Java的程序中使用這些方法。咱們能夠在調用這些方法時指定類型,也能夠像普通方法同樣調用它們。Java編譯器足夠聰明,能夠肯定要使用的變量的類型,這種功能稱爲類型變量。
假設咱們要限制能夠在參數化類型中使用的對象的類型,例如在比較兩個對象的方法中,而且咱們要確保接受的對象是可比較的。要聲明一個有界的類型參數,請列出類型參數的名稱,而後列出擴展關鍵字,再加上其上限,如下下面的方法。
public static <T extends Comparable<T>> int compare(T t1, T t2){ return t1.compareTo(t2); }
這些方法的調用與無界方法相似,不一樣之處在於,若是咱們嘗試使用任何非Comparable的類,則引起編譯時錯誤。
綁定類型參數能夠與方法以及類和接口一塊兒使用。
Java泛型也支持多個範圍,即<T擴展A&B&C>。在這種狀況下,A能夠是接口或類。若是A是類,則B和C應該是接口。在多個範圍內,咱們不能有多個類。
咱們知道,若是A是B的子類,則Java繼承容許咱們將變量A分配給另外一個變量B。所以,咱們可能認爲能夠將A的任何泛型類型分配給B的泛型類型,但事實並不是如此。讓咱們用一個簡單的程序看看。
package com.journaldev.generics; public class GenericsInheritance { public static void main(String[] args) { String str = "abc"; Object obj = new Object(); obj=str; // works because String is-a Object, inheritance in java MyClass<String> myClass1 = new MyClass<String>(); MyClass<Object> myClass2 = new MyClass<Object>(); //myClass2=myClass1; // compilation error since MyClass<String> is not a MyClass<Object> obj = myClass1; // MyClass<T> parent is Object } public static class MyClass<T>{} }
咱們能夠經過擴展或實現來泛型一個通用類或接口。一個類或接口的類型參數與另外一類或接口的類型參數之間的關係由extend和實現子句肯定。
例如,ArrayList
只要不更改type參數,子類型關係就會保留,下面顯示了多個type參數的示例。
interface MyList<E,T> extends List<E>{ }
List
問號(?)是泛型中的通配符,表示未知類型。通配符能夠用做參數,字段或局部變量的類型,有時還能夠用做返回類型。在調用通用方法或實例化通用類時,不能使用通配符。在如下各節中,咱們將學習上界通配符,下界通配符和通配符捕獲。
9.1)Java泛型上界通配符
上限通配符用於在方法中放寬對變量類型的限制。假設咱們要編寫一個將返回列表中數字總和的方法,那麼咱們的實現將是這樣的。
public static double sum(List<Number> list){ double sum = 0; for(Number n : list){ sum += n.doubleValue(); } return sum; }
如今,上述實現的問題在於它不適用於Integers或Doubles,由於咱們知道List
能夠像下面的程序同樣修改上面的實現。
package com.journaldev.generics; import java.util.ArrayList; import java.util.List; public class GenericsWildcards { public static void main(String[] args) { List<Integer> ints = new ArrayList<>(); ints.add(3); ints.add(5); ints.add(10); double sum = sum(ints); System.out.println("Sum of ints="+sum); } public static double sum(List<? extends Number> list){ double sum = 0; for(Number n : list){ sum += n.doubleValue(); } return sum; } }
就像按照接口編寫代碼同樣,在上述方法中,咱們可使用上限類號碼的全部方法。請注意,對於上界列表,除空以外,咱們不容許將任何對象添加到列表中。若是咱們嘗試在sum方法內將元素添加到列表中,則該程序將沒法編譯。
9.2)Java泛型無限制通配符
有時,咱們但願通用方法適用於全部類型,在這種狀況下,可使用無界通配符。與使用<?extends Object>。
public static void printData(List<?> list){ for(Object obj : list){ System.out.print(obj + "::"); } }
咱們能夠爲_PrintData_方法提供List
9.3)Java泛型下界通配符
假設咱們要在方法中將總體添加到整數列表中,咱們能夠將參數類型保持爲List