java泛型通俗講解
1.為什麼需要泛型
我們知道java是屬於強型別程式語言。變數在使用之前,需要先進行定義,而定義個變數時必須要指定其資料型別,這樣編譯器在編譯階段就能將很多型別錯誤消滅在萌芽狀態。
如果我們有這樣一個需求:定義一個座標類。但是該座標類的資料型別可能是整數,也可以能是小數,還有可能是字串。舉個例子
x=10; y=15;
x="東經116",y="北緯39"
x=10.01; y=15
座標有可能是整型,也有可能是字串型別,還有可能是double型別。如果沒有泛型,我們可能會這麼做
package edu.bit.test;
public class ObjectType {
public static void main(String[] args) {
Point p = new Point();
p.setX(10);
p.setY(15);
//向下轉型,此時沒有問題,程式碼能正常執行
int x = (Integer)p.getX();
int y = (Integer)p.getY();
System.out.println(p);
p.setX("東經116");
p.setY("北緯39");
//此時向下轉型會有問題
double x1 = (Double)p.getX();
double y1 = (Double)p.getY();
System.out.println(p);
}
}
class Point {
Object x = 0;
Object y = 0;
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;
}
@Override
public String toString() {
return "Point{" +
"x=" + x +
", y=" + y +
'}';
}
}
將程式碼run起來
Point{x=10, y=15}
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Double
at edu.bit.test.ObjectType.main(ObjectType.java:22)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
Process finished with exit code 1
在上面的示例程式碼中,用的是Object型別。這樣,在呼叫set方法的時候是沒有問題的。但是在使用get方法時,因為需要將Object類向下轉型成其他類,這樣容易帶來問題。並且,這種問題在jdk編譯階段還不容易被發現,往往只有在執行的時候才會丟擲異常!像我們上面的程式碼,在IDE中是不會報錯的,但是程式碼run起來以後,會丟擲一個ClassCastException的異常!
2.泛型的使用方式
將上面的程式碼改用泛型的方式實現
package edu.bit.test;
public class GenericType {
public static void main(String[] args) {
Point1<Integer,Integer> p1 = new Point1<Integer, Integer>();
p1.setX(10);
p1.setY(15);
System.out.println(p1);
Point1<String,String> p2 = new Point1<String,String>();
p2.setX("東經116");
p2.setY("北緯39");
System.out.println(p2);
}
}
class Point1<T1,T2> {
T1 x;
T2 y;
public T1 getX() {
return x;
}
public void setX(T1 x) {
this.x = x;
}
public T2 getY() {
return y;
}
public void setY(T2 y) {
this.y = y;
}
@Override
public String toString() {
return "Point1{" +
"x=" + x +
", y=" + y +
'}';
}
}
將程式碼run起來
Point1{x=10, y=15}
Point1{x=東經116, y=北緯39}
在上面的例子裡,直接將Point1類定義為泛型類。泛型類的定義方法為類名後面用尖括號將
3.限制泛型的使用型別
在我們上面的程式碼中,沒有對引數型別做任何限制,可以使用任何型別的引數。然後在很多實際場景中,還是需要對引數型別做一定限制的,只能傳遞部分引數型別,傳遞其他型別則會引發錯誤。例如在前面的Point類中,我們希望使用者只能傳遞數字型別,而不能傳遞字串等其他型別:
package edu.bit.test;
public class GenericSmallType {
public static void main(String[] args) {
Point2<Integer,Integer> p1 = new Point2<Integer,Integer>();
p1.setX(10);
p1.setY(15);
System.out.println(p1);
Point2<Double,Double> p2 = new Point2<Double,Double>();
p2.setX(10.0);
p2.setY(15.01);
System.out.println(p2);
}
}
class Point2<T1 extends Number,T2 extends Number> {
public T1 x;
public T2 y;
public T1 getX() {
return x;
}
public void setX(T1 x) {
this.x = x;
}
public T2 getY() {
return y;
}
public void setY(T2 y) {
this.y = y;
}
@Override
public String toString() {
return "Point2{" +
"x=" + x +
", y=" + y +
'}';
}
}
將程式碼run起來:
Point2{x=10, y=15}
Point2{x=10.0, y=15.01}
4.型別擦除(type erasure)
java位元組碼中不包含有泛型的型別資訊。編譯器在編譯階段去掉泛型資訊,在執行的時候加上型別資訊,這個過程被稱為型別擦除。
我們在使用泛型的過程中,如果沒有指定資料型別,那麼將會擦除泛型型別。
package edu.bit.test;
public class TypeErasure {
public static void main(String[] args) {
Point3 p1 = new Point3();
p1.setX("東經116");
p1.setY("北緯39");
//向下轉型
String longitude = (String)p1.getX();
String latitude = (String)p1.getY();
System.out.println(p1);
}
}
class Point3<T1,T2> {
public T1 x;
public T2 y;
public T1 getX() {
return x;
}
public void setX(T1 x) {
this.x = x;
}
public T2 getY() {
return y;
}
public void setY(T2 y) {
this.y = y;
}
@Override
public String toString() {
return "Point3{" +
"x=" + x +
", y=" + y +
'}';
}
}
本例中,建立Point3物件的時候沒有指定資料型別。編譯器在處理的時候,會將所有資料向上變為Object型別。然而這樣處理完以後,使用get方法的時候,又需要向下轉型。這樣的話,跟第一部分沒有使用泛型就一樣了!