一、泛型的概念
我們經常需要對多個類型的數據做相同的操作,但爲各個類型分別編寫方法和類是十分低效的。爲此,JDK-5引入了泛型。
泛型提供了編譯時類型安全檢測機制,該機制允許程序員在編譯時檢測到非法的類型。
泛型的實質是類型參數化,將數據類型作爲參數進行傳遞。
類型參數必須使用引用型類型。
二、泛型
1.泛型方法
泛型方法可以在調用時接收不同類型的參數、規定不同類型的返回值。
一個泛型方法定義如下:
<類型參數聲明>返回類型 方法名(形參表){
//方法定義
}
下面是簡單的舉例,並使用兩種方法調用泛型方法:
public class Demo {
public static void main(String[] args) {
Integer i = 123;
Demo.<Integer>print(i); //給定類型參數調用,該方法必須配合使用句點表示法,無法自動推斷時使用
print(i); //Java8新增類型推斷,一般採用該方法
}
public static <T> void print(T t) {
System.out.println(t);
}
}
如果想要的類型參數在某一個特定範圍內(通常是繼承層次中的範圍),可以通過使用extends關鍵字。這個關鍵詞規定了類型實參必須繼承了某個類,或者實現了某個接口。
//如沒有extends,編譯無法通過,因爲不知道T是否定義了compareTo方法
public <T extends Comparable<T>> boolean smallerThan(T o1,T o2) {
if(o1.compareTo(o2) < 0)
return true;
else
return false;
}
泛型方法也可以使用可變參數,如:
public <T> void print(T...args) {
for(T t : args)
System.out.println(t);
}
2.泛型類
泛型類可以對不同類型的數據開放相同的接口。最典型的就是各種容器類。
一個泛型類定義如下:
class 類名<類型參數聲明>{
//實例/局部變量和實例方法可以使用類聲明的類型參數
}
下面是一個簡單的舉例及兩種使用方法:
class Test<T>{
T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
}
public class Demo {
public static void main(String[] args) {
//方法一:給定類型實參,實例對象只能接受給定類型的數據
Test<Integer> test1 = new Test<Integer>();
test1.set(6);
//方法二:不給定類型實參,實例對象可以接受任何類型的數據
Test test2 = new Test();
test2.set(1);
test2.set(2.0);
test2.set("3");
}
}
泛型方法每次調用時都會重新壓棧,重新推斷類型,因此是安全的。在可以自動推斷的情況下應儘量使用。
泛型類的實例一旦初始化,在其作用域內就始終存在。中途改變其類型參數是不安全的,應使用給定實參的方式。
● 泛型類中的泛型方法
並不是所有泛型類中的方法都是泛型方法,如:
class Test<T>{
T value;
public void set(T value) { //不是一個泛型方法,只是使用了類聲明的類型形參
this.value = value;
}
public T get() { //同上,不是一個泛型方法
return value;
}
public <E> void print(E arg){ //這是一個泛型方法,他有單獨的類型參數聲明,這個方法中T和E均可使用
System.out.print(arg.toString());
}
public <T> void println(T arg) { //這也是一個泛型方法,但它將類給定的類型參數T隱藏(屏蔽)了,這裏的T是自身的類型形參
System.out.println(arg.toString());
}
//這兩個方法主要是爲了說明泛型類中的泛型方法,明確概念,兩個函數本身不具應用意義
}
● 靜態方法與泛型
靜態方法無法訪問實例成員,而類型參數也是實例成員的一種,因此靜態方法不可使用類的類型參數。
例如在上面的類中添加如下的靜態方法是不能通過編譯的:
public static void print2(T arg){ //Error:不能對非靜態類型T進行靜態引用
System.out.println(arg);
}
應當爲靜態方法給出一個自身的類型參數聲明,修改如下:
public static <T> void print2(T arg) {
System.out.println(arg);
}
3.泛型接口
泛型接口的定義與泛型類基本相同,常被用在各種類的生產器,例如比較器。
interface Print <T>{ //輸出器
public void print(T o);
}
使用類實現泛型接口時,需要爲泛型接口傳遞類型參數。常見的是泛型類爲泛型接口傳遞形參:
interface Print <T>{
public void print(T o);
}
class Test<T> implements Print<T>{
@Override
public void print(T o) {
System.out.println(o);
}
}
4.類型通配符
當同一個泛型類被不同的類型參數實例化後,就變成了不同的版本。版本不同的實例是不兼容的,哪怕是基類與子類之間。
public class Demo {
public static void main(String[] args) {
test(new Test<Base>()); //無錯誤
test(new Test<Heir>()); //Error:方法 test(Test<Base>)對於參數(Test<Heir>)不適用
}
public static void test(Test<Base> arg) { //規定僅接受Test<Base>版本,<>裏必須給定類型,否則報錯
System.out.println(arg);
}
}
class Base{}
class Heir extends Base{}
class Test<T>{}
爲了解決這一問題,我們使用 ? 作爲類型通配符,它可以指代所有的類型。將上面的test方法作如下修改,就可以通過編譯。
//public static void test(Test<Base> arg)
public static void test(Test<?> arg) {
System.out.println(arg);
}
5.泛型的上下邊界
使用泛型時,我們可以爲接受的類型參數界定上下邊界。
規定傳入類型必須是某個類型的子類/實現某個接口(上界),或必須是某個類型的父類(下界)。
extends 關鍵字用於規定上界,規定上界可以允許泛型中使用父類的實例成員,並允許多態。
public class Demo {
public static void main(String[] args) {
test(new Test<Base>(new Base())); //class learning_test.Base
test(new Test<Heir>(new Heir())); //class learning_test.Heir
}
public static void test(Test<? extends Base> arg) {
arg.print();
}
}
class Base{}
class Heir extends Base{}
class Test<T>{
T value;
public Test (T value) {
this.value = value;
}
public void print() {
System.out.println(value.getClass().toString());
}
}
super關鍵字用於規定下界,即只接收給定類型的父類類型。將上面的test形參該位Test<? super Heir>,main方法中會報錯。
test(new Test<Base>(new Base())); //無措
test(new Test<Heir>(new Heir())); //Error:方法 test(Test<? super Base>)對於參數(Test<Heir>)不適用
測試時發現,extends對於任意的泛型都可以使用,但super只能與類型通配符一起使用。
public static <T extends Base> void print1(T o){} //無錯誤
public static <T super Base> void print2(T o){} //Error:標記“super”上有語法錯誤,應爲,
//第二行的錯誤報告看上去很奇怪,可能是將super識別爲父類關鍵字super使用了
public static void print3(List<? extends Base> list) {} //無錯誤
public static void print4(List<? super Base> list) {} //無錯誤