在學習泛型之前我們先回顧下JAVA的數據類型以及涉及到的一些概念。
Java數據類型
Java的兩大數據類型分為基礎類型和引用類型。基本類型的數值不是對象,不能調用對象的toString()、hashCode()、getClass()、equals()等方法。
自動裝箱
把基本類型用它們對應的引用類型包裝起來,使它們具有對象的特質,可以調用toString()、hashCode()、getClass()、equals()等方法。
例如:
//自動裝箱
Integer i=1;
而實際上編譯器會調用static Integer valueOf(int i)這個方法,返回一個表示指定int值的Integer對象。Integer i=1; ->. Integer i=Integer.valueOf(1);
拆箱
跟自動裝箱的方向相反,將引用類型轉換為基本類型。
//拆箱
int i = new Integer(1);
自動裝箱和拆箱是由編譯器來完成的,編譯器會在編譯期根據語法決定是否進行裝箱和拆箱動作。
假如我們定義一個類來表示坐標,要求類中基礎類型可以為整數 、小數、字符串,例如:
Object x=116,y=54;
Object x=116.92,y=54.31;
Object x="經度116.92",y="緯度54.31";
我們知道,基本數據類型可以自動裝箱,被轉換成對應的包裝類。Object 是所有類的祖先類,任何一個類的實例都可以向上轉型為 Object 類型,例如:
int -> Integer -> Object
double -> Double -> Object
String -> Object
泛型的使用
如果要取出坐標值就需要向下轉型,向下轉型存在著風險,而且編譯期間不容易發現,只有在運行期間才會拋出異常,所以要盡量避免使用向下轉型。例如下面的實例:
public class Test {
public static void main(String[] args){
Point point = new Point();
//int -> Integer -> Object
point.setX(116);
point.setY(54);
//向下轉型
int x = (Integer) point.getX();
int y = (Integer) point.getY();
System.out.println("Point :x="+x+" y="+y);
//double -> Double -> Object
point.setX(116.92);
point.setY("緯度54.32");
//向下轉型
Double x1 = (Double) point.getX();
//會拋出ClassCastException異常
Double y1 = (Double) point.getY();
System.out.println("Point :x1="+x1+" y1="+y1);
}
}
class Point{
Object x = null;
Object y = null;
public Object getX() {
return x;
}
public void setX(Object x) {
this.x = x;
}
public Object getY() {
return y;
}
public void setY(Object y) {
this.y = y;
}
}
那么Java中如何避免這樣的情況發生呢?
Java中泛型出現就是解決出現這樣的問題,所謂的“泛型”就是任意的數據類型。以下代碼將利用泛型解決上面的問題。
public class Test {
public static void main(String[] args){
//實例化泛型
Point<Integer,Integer> point = new Point<Integer,Integer>();
point.setX(116);
point.setY(54);
int x = point.getX();
int y = point.getY();
System.out.println("Point :x="+x+" y="+y);
Point<Double,String> point1 = new Point<Double,String>();
point1.setX(116.92);
point1.setY("緯度54.32");
Double x1 = point1.getX();
String y1 = point1.getY();
System.out.println("Point :x1="+x1+" y1="+y1);
}
}
//定義泛型類
class Point<T1,T2>{
T1 x = null;
T2 y = null;
public T2 getY() {
return y;
}
public void setY(T2 y) {
this.y = y;
}
public T1 getX() {
return x;
}
public void setX(T1 x) {
this.x = x;
}
}
實例中的T1和T2叫類型參數,類型參數是用來表示自定義標識符,用來傳遞數據的類型。Java中傳值參數用小括號包圍,泛型參數用尖括號包圍,多個參數用逗號間隔,例如:
<T>或<T,E>
類型參數需要在類名后面給出。一旦給出了類型參數,就可以在類中使用了。類型參數必須是一個合法的標識符,習慣上使用單個大寫字母,通常情況下,K 表示鍵,V 表示值,E 表示異常或錯誤,T 表示一般意義上的數據類型。
泛型類在實例化時必須指出具體的類型,也就是向類型參數傳值,格式為:
className variable<dataType1, dataType2> = new className<dataType1, dataType2>();
也可以省略等號右邊的數據類型,但是會產生警告,即:
className variable<dataType1, dataType2> = new className();
泛型的優點:使用泛型類時指明了數據類型,賦給其他類型的值會拋出異常,既不需要向下轉型,也沒有潛在的風險。
除了定義泛型類,還可以定義泛型接口和泛型方法,使用泛型方法時不必指明參數類型,編譯器會根據傳遞的參數自動查找出具體的類型。
限制泛型的可用類型
通過 extends 關鍵字可以限制泛型的類型
public <T extends Number> T getMax(T array[]){
T max = null;
for(T element : array){
max = element.doubleValue() > max.doubleValue() ? element : max;
}
return max;
}
<T extends Number> 表示 T 只接受 Number 及其子類,傳入其他類型的數據會報錯。這里的限定使用關鍵字 extends,后面可以是類也可以是接口。但這里的 extends 已經不是繼承的含義了,應該理解為 T 是繼承自 Number 類的類型。