Java 泛型
泛型入門
編譯時不檢查類型的異常
public class ListErr
{
public static void main(String[] args)
{
// 創建一個只想保存字符串的List集合
List strList = new ArrayList();
strList.add("布達佩斯");
strList.add("布拉格");
// "不小心"把一個Integer對象"丟進"了集合
strList.add(34); // ①
strList.forEach(str -> System.out.println(((String)str).length())); // ②
}
}
上述程序創建了一個List集合,且該List集合保存字符串對象——但程序不能進行任何限制,如果程序在①處“不小心”把一個Integer對象"丟進"了List集合,這將導致程序在②處引發ClassCastException異常,因為程序試圖把一個Integer對象轉換為String類型
使用泛型
參數化類型,允許程序在創建集合時指定集合元素的類型。Java的參數化類型被稱為泛型(Generic)
class GenericList
{
public static void main(String[] args)
{
// 創建一個只想保存字符串的List集合
List<String> strList = new ArrayList<>(); // ①
strList.add("布達佩斯");
strList.add("布拉格");
// 下面代碼將引起編譯錯誤
strList.add(34); // ②
strList.forEach(str -> System.out.println(((String)str).length())); // ③
}
}
strList集合只能保存字符串對象,不能保存其他類型的對象。創建特殊集合的方法是:在集合接口、類后增加尖括號,尖括號里放一個數據類型,即表明這個集合接口、集合類只能保存特定類型的對象。
①類型聲明,在創建這個ArrayList對象時也指定了一個類型參數;②引發編譯異常;③不需要進行強制類型轉換
泛型使程序更加簡潔,集合自動記住所有集合元素的數據類型,從而無須對集合元素進行強制類型轉換
Java7泛型的“菱形”語法
Java允許在構造器后不需要帶完整的泛型信息,只要給出一對尖括號(<>)即可,Java可以推斷尖括號里應該是什么泛型信息
public class DiamondTest
{
public static void main(String[] args)
{
// Java自動推斷出ArrayList的<>里應該是String
List<String> countries = new ArrayList<>();
countries.add("法蘭西第五共和國");
countries.add("西班牙王國");
// 遍歷countries集合,集合元素就是String類型
countries.forEach(ele -> System.out.println(ele.length()));
// Java自動推斷出HashMap的<>里應該是String , List<String>
Map<String , List<String>> citiesInfo = new HashMap<>();
// Java自動推斷出ArrayList的<>里應該是String
List<String> cities = new ArrayList<>();
cities.add("巴黎");
cities.add("巴塞羅那");
citiesInfo.put("Bienvenue" , cities);
// 遍歷Map時,Map的key是String類型,value是List<String>類型
citiesInfo.forEach((key , value) -> System.out.println(key + "-->" + value));
}
}
深入泛型
所謂泛型:就是允許在定義類、接口、方法時指定類型形參,這個類型形參將在聲明變量、創建對象、調用方法時動態地指定(即傳入實際的類型參數,也可稱為類型實參)
定義泛型接口、類
List接口、Iterator接口、Map的代碼片段
// 定義接口時指定了一個類型形參,該形參名為E
public interface List<E>
{
// 在該接口里,E可作為類型使用
// 下面方法可以使用E作為參數類型
void add(E x);
Iterator<E> iterator(); // ①
}
// 定義接口時指定了一個類型形參,該形參為E
public interface Iterator<E>
{
// 在該接口里E完全可以作為類型使用
E next();
boolean hasNext();
}
// 定義該接口時指定了兩個類型形參,其形參名為K、V
public interface Map<K, V>
{
// 在該接口里K、V完全可以作為類型使用
Set<K> keySet(); // ②
V put(K key,V value);
}</code></pre>
上面代碼說明泛型實質:允許在定義接口、類時聲明類型形參,類型形參在整個接口、類體內可當成類型使用
包含泛型聲明的類型可以在定義變量、創建對象時傳入一個類型實參,從而可以動態地生成無數個邏輯上的子類,但這種子類在物理上并不存在
可以為任何類、接口增加泛型聲明(并不是只有集合類才可以使用泛型聲明,雖然集合類是泛型的重要使用場所)
// 定義Onmyoji類時使用了泛型聲明
public class Onmyoji<T>
{
// 使用T類型形參定義實例變量
private T info;
public Onmyoji(){}
// 下面方法使用T類型形參來定義構造器
public Onmyoji(T info)
{
this.info = info;
}
public T getInfo()
{
return info;
}
public void setInfo(T info)
{
this.info = info;
}
public static void main(String[] args)
{
//由于傳給T形參的是String,所以構造器參數只能是String
Onmyoji<String> a1 = new Onmyoji<>("安倍晴明");
System.out.println(a1.getInfo());
// 由于傳給T形參的是Double,所以構造器參數只能是Double或double
Onmyoji<Double> a2 = new Onmyoji<>(520.1314);
System.out.println(a2.getInfo());
}
}
當創建帶泛型聲明的自定義類,為該類定義構造器時,構造器名還是原來的類名,不要增加泛型聲明
從泛型類派生子類
當創建了帶泛型聲明的接口、父類之后,可以為該接口創建實現類,或從父類派生子類。當使用這些接口、父類時不能再包含類型形參
// 定義類Shikigami繼承Onmyoji類,Onmyoji類不能跟類型形參
public class Shikigami extends Onmyoji<T>{ } // 錯誤
方法中的形參(或數據形參)代表變量、常量、表達式等數據。定義方法時,可以聲明數據形參;調用方法(使用方法)時,必須為這些數據形參傳入實際的數據;與此類似的是,定義類、接口、方法時可以聲明類型形參,使用類、接口、方法時應為類型形參傳入實際的類型
// 使用Onmyoji類時為T形參傳入String類型
public class Shikigami extends Onmyoji<String>{ } // 正確
調用方法時必須為所有的數據參數傳入參數值,而使用類、接口時也可以不為類型形參傳入實際的類型參數
// 使用Onmyoji類時,沒有為T形參傳入實際的類型參數
public class Shikigami extends Onmyoji{ } // 正確
子類需要重寫父類的Getters和Setters方法
private String info;
public String getInfo()
{
return "子類"+ super.getInfo();
}
public void setInfo(String info)
{
this.info = info;
}
并不存在泛型類
ArrayList<String>類,是一種特殊的ArrayList類。該ArrayList<String>對象只能添加String對象作為集合元素。但實際上,系統并沒有為ArrayList<String>生成新的class文件,而且也不會把ArrayList<String>當成新類來處理。因為不管泛型的時間類型參數是什么,它們在運行時總有同樣的類(class)
// 分別創建List<String>對象和List<Integer>對象
List<String> l1 = new ArrayList<>();
List<Integer> l2 = new ArrayList<>();
// 調用getClass()方法來比較l1和l2的類是否相等
System.out.println(l1.getClass() == l2.getClass()); // 輸出true
不管為泛型的類型形參傳入哪一種類型實參,對于Java來說,它們依然被當成同一個類處理,在內存中也只占用一塊內存空間,因此在靜態方法、靜態初始化塊或者靜態變量的聲明和初始化中不允許使用類型形參
由于系統中并不會真正生成泛型類,所以instanceof運算符后不能使用泛型類。
if(cs instanceof List<String>)
{
...
}
類型通配符
如果Foo是Bar的一個子類型(子類或者子接口),而G是具有泛型聲明的類或接口,G<Foo>并不是G<Bar>的子類型
數組和泛型有所不同,假設Foo是Bar的一個子類型(子類或者子接口),那么Foo[]依然是Bar[]的子類型;但G<Foo>不是G<Bar>的子類型
Java泛型的設計原則是,只要代碼在編譯時沒有出現警告,就不會遇到運行時ClassCastException異常
使用類型通配符
為了表示各種泛型List的父類,可以使用類型通配符,類型通配符是一個問號(?),將一個問號作為類型實參傳給List集合,寫作:List<?>(意思是未知類型元素的List)。這個問號(?)被稱為通配符,它的元素類型可以匹配任何類型
public void test(List<?> c)
{
for(int i = 0; i < c.size(); i++)
{
System.out.println(c.get(i));
}
}
現在使用任何類型的List來調用它,程序依然可以訪問集合c中的元素,其類型是Object,這永遠是安全的,因為不管List的真實類型是什么,它包含的都是 Object
這種帶通配符的List僅表示它是各種泛型List的父類,并不能把元素加入到其中
List<?> c = new ArrayList<String>();
// 下面程序引起編譯錯誤
c.add(new Object());
因為程序無法確認c集合里元素的類型,所以不能向其中添加對象。根據前面的List<E>接口定義的代碼可以發現:add ()方法由類型參數E作為集合的元素類型,所以傳給add的參數必須是E類的對象或者其子類的對象。但因為在該例中不知道E是什么類型,所以程序無法將任何對象“丟進”該集合。唯一的例外是 null,它是所有引用類型的實例
另一方面,程序可以調用get()方法來返回List<?>集合指定索引處的元素,其返回值是一個未知類型,但可以肯定的是,它總是一個Object。因此,把get()的返回值賦值給一個Object類型的變量,或者放在任何希望是Object類型的地方都可以
設定類型通配符的上限
List<Circle>并不是List<Shape>的子類型,所以不能把List<Circle>對象當成List<Shape>使用。為了表示List<Circle>的父類,使用List<? extends Shape>
List<? extends Shape>是受限通配符的例子,此處的問號(?)代表一個未知的類型,此處的未知類型一定是Shape的子類也可以是Shape,因此可以把shape稱為這個通配符的上限(upper bound)
設定類型形參的上限
Java泛型不僅允許在使用通配符形參時設定上限,而且可以在定義類型形參時設定上限,用于表示傳給該類型形參的實際類型要么是該上限類型,要門是該上限類型的子類
在一種更極端的情況下,程序需要為類型形參設定多個上限(至少有一個父類上限,可以有多個接口上限),表明該類型形參必須是其父類的子類(其父類本事也行),并且實現多個上限接口
//表明T類型必須是Number類或其子類,并必須實現java.io.Serializablepublic
class Apple<T extends Number & java.io.Serilizable>
{
...
}
與類同時繼承父類、實現接口類似的是:為類型形參指定多個上限,所有的接口上限必須位于類上限之后。也就是說,如果需要為類型形參指定類上限,類上限必須位于第一位
泛型方法
定義泛型方法
Java不允許把對象放進一個未知類型的集合中
所謂泛型方法,就是在聲明方法時定義一個或多個類型形參
修飾符 <T, S> 返回值類型 方法名(形參列表)
{
// 方法體...
}
來自:https://segmentfault.com/a/1190000007983793