Java 泛型詳解(一)
技術標籤:Java
一、泛型的引出
假設要求定義一個表示座標的操作類(Point),這個類可以表示三種類型的座標:
(1)整數座標:x = 10、y = 20;
(2)小數座標:x = 10.1、y = 20.3;
(3)字串資料:x = “東經100度”、y = “北緯20度”。
分析:
類中如果想儲存以上的資料,一定需要定義x和y兩個屬性,而這兩個屬性可以接收三種資料型別,這樣的話,只能使用Object類來定義會比較合適,這樣會發生如下的幾種轉換關係:
(1)整數:int - >自動裝箱為Integer->向上轉型為Object;
(2)小數:double -> 自動裝箱為Double-> 向上轉型為Object;
於是,程式碼定義如下:
public class Point {
private Object x;
private Object y;
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;
}
}
下面開始設定不同的資料型別,以測試程式。
public class Test {
public static void main(String[] args) throws Exception {
//設定整型
Point point1 = new Point() ;
point1.setX(10) ;
point1.setY(20) ;
int x1 = (Integer) point1.getX() ;
int y1 = (Integer) point1.getY() ;
System.out.println("X的座標是:" + x1 + ",Y的座標是:" + y1);
//設定小數
Point point2 = new Point() ;
point2.setX(10.1) ;
point2.setY(20.3) ;
double x2 = (Double) point2.getX() ;
double y2 = (Double) point2.getY() ;
System.out.println("X的座標是:" + x2 + ",Y的座標是:" + y2);
//設定字串
Point point3 = new Point() ;
point3.setX("東經100度") ;
point3.setY("北緯20度") ;
String x3 = (String) point3.getX() ;
String y3 = (String) point3.getY() ;
System.out.println("X的座標是:" + x3 + ",Y的座標是:" + y3);
}
}
執行結果:
X的座標是:10,Y的座標是:20
X的座標是:10.1,Y的座標是:20.3
X的座標是:東經100度,Y的座標是:北緯20度
現在,看起來功能都實現了,但是本程式是否有問題?
本程式解決問題的關鍵就在於Object類,所有的型別都可以向Object轉換,但是成是Object,敗也是Object:
public class Test{
public static void main(String[] args) throws Exception {
Point point = new Point() ;
point.setX(10) ; // 此處設定成int型(Integer型)
point.setY("北緯20度") ;
String x = (String) point.getX() ;
String y = (String) point.getY() ;
System.out.println("X的座標是:" + x + ",Y的座標是:" + y);
}
}
這時程式並沒有出現任何語法錯誤,因為數字10被裝箱成了Integer,可以使用Object接收,從技術上而言,本操作沒有問題,但是從實際來講,資料是有錯誤的,因為沒有統一,所以在取得資料並且執行向下轉型的過程之中就會出現如下的錯誤提示資訊:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
at Test.main(Test.java:7)
所以,以上的程式碼存在安全隱患,並且這一安全隱患並沒有在程式編譯的過程中檢查出來。現在可以利用泛型來解決這種尷尬的問題!
二、泛型的定義和使用
泛型的本質是引數化型別,也就是說所操作的資料型別被指定為一個引數。類中操作的屬性或方法的引數的型別不在定義的時候宣告,而是在使用的時候動態設定。
上面Point類利用泛型可以做如下定義:
class Point<T> { // T:Type
private T x;
private T y;
public void setX(T x) {
this.x = x;
}
public void setY(T y) {
this.y = y;
}
public T getX() {
return x;
}
public T getY() {
return y;
}
}
此時,point類中的x和y兩個屬性的型別暫時還不知道,等待程式執行的時候動態設定。
測試:
public class Test {
public static void main(String[] args) throws Exception {
Point<String> point = new Point<String>();
point.setX("東經100度");
point.setY("北緯20度");
String x = point.getX();
String y = point.getY();
System.out.println("X的座標是:" + x + ",Y的座標是:" + y);
}
}
執行結果:
X的座標是:東經100度,Y的座標是:北緯20度
此時,沒了向下轉型的操作關係,程式就避免了安全性的問題。如果設定的型別不統一,在程式編譯的過程中也是可以很好的解決了,直接會報出語法錯誤。
而當用戶在使用Point類宣告物件的時候沒有設定泛型,那麼程式在編譯的過程之中會出現警告資訊。使用泛型可以很好的解決資料型別的統一問題。
注意:
JDK 1.5和JDK 1.7在定義泛型的時候是稍微有些區別的,
JDK 1.5的語法,在宣告物件和例項化物件的時候必須都同時設定好泛型型別:
Point<String> point = new Point<String>() ;
JDK 1.7的時候簡化了,例項化物件時的泛型型別就通過宣告時的泛型型別來定義:
Point<String> point = new Point<>() ;
三、泛型萬用字元
泛型的出現的確可以解決了資料型別的統一問題以及避免了向下轉型操作,但同時又會帶來新的問題,下面先通過一段程式來觀察一下會產生什麼問題?
為了簡化操作,下面定義一個簡單的泛型類:
class Message<T> {
private T info;
public void setInfo(T info) {
this.info = info;
}
public T getInfo() {
return info;
}
}
使用以上類物件執行引用傳遞:
public class Test {
public static void main(String[] args) throws Exception {
Message<String> msg = new Message<String>();
msg.setInfo("Hello World .");
print(msg); // 引用傳遞
}
public static void print(Message<String> temp) {
System.out.println(temp.getInfo()); // 只是輸出
}
}
但是,如果現在定義的泛型型別不是String了呢?例如,換成了int(不能寫基本型別,只能是包裝類):
public class Test {
public static void main(String[] args) throws Exception {
Message<Integer> msg = new Message<Integer>();
msg.setInfo(100);
print(msg); // 無法進行引用傳遞
}
public static void print(Message<String> temp) {
System.out.println(temp.getInfo()); // 只是輸出
}
}
發現這個時候的print()方法無法再接收Message<Integer>
物件的引用,因為這個方法只能夠接收Message<String>
物件的引用,那麼可以將print()方法過載嗎?,換成Message<Integer>
:
public class Test {
public static void main(String[] args) throws Exception {
Message<Integer> msg = new Message<Integer>();
msg.setInfo(100);
print(msg); // 無法進行引用傳遞
}
public static void print(Message<String> temp) {
System.out.println(temp.getInfo()); // 只是輸出
}
public static void print(Message<Integer> temp) {
System.out.println(temp.getInfo()); // 只是輸出
}
}
但是,這時候發現按照之前的方式根本就無法進行方法的過載,因為方法過載的時候觀察的不是泛型型別,而是類的名稱,或者是說資料型別的,那麼現在就可以發現,這個給出了泛型類之後,就相當於將一個類又劃分成了若干個不同的小型別:
那麼現在的問題:方法接收的引數問題又嚴重了,而且比之前使用物件多型性解決問題時出現的麻煩更大了,至少那個時候可以利用過載來接收一個類的所有子類物件,而現在呢連過載的機會都不給了。
這個時候,有人提出了,乾脆在定義方法的時候就別寫泛型型別了。
定義方法時不寫上泛型型別:
public class Test {
public static void main(String[] args) throws Exception {
Message<Integer> msg = new Message<Integer>();
msg.setInfo(100);
print(msg);
}
public static void print(Message temp) {
System.out.println(temp.getInfo());
}
}
雖然現在在print()方法的引數上出現了警告,但是現在的程式可算是正常了。但是新的問題又來了,問題就在於方法操作中,沒有型別限制了:
public static void print(Message temp) {
temp.setInfo(100); // 設定Integer
System.out.println(temp.getInfo()); // 只是輸出
}
發現此時在print()方法之中操作的時候,由於沒有設定泛型型別,那麼所有的型別都統一變為了Object,也就可以修改了,而通過本程式也就發現了,必須找到一種方法:此方法可以接收任意的泛型型別的設定,並且不能夠修改,只能夠輸出。為了解決這樣的問題,可以使用萬用字元“?”表示:
public static void print(Message<?> temp) {
System.out.println(temp.getInfo());
}
在萬用字元“?”上有兩個子萬用字元:
(1)設定泛型的上限:
? extends 類
可以在宣告上和方法引數上使用,例如:
? extends Number
表示:只能是Number或者是Number的子類Integer、Double等。
(2)設定泛型的下限:
? super 類
方法引數上使用,例如:
? super String
表示只能是String或者是String的父類(Object類)。
範例:設定泛型上限
class Message<T extends Number> {
private T info;
public void setInfo(T info) {
this.info = info;
}
public T getInfo() {
return info;
}
}
public class Test {
public static void main(String[] args) throws Exception {
Message<Integer> msg = new Message<Integer>(); // Integer是Number的子類
msg.setInfo(100);
print(msg); // 引用傳遞
}
public static void print(Message<?> temp) {
System.out.println(temp.getInfo()); // 只是輸出
}
}
執行結果:
100
範例:設定泛型下限,在方法的引數上使用
class Message<T> {
private T info;
public void setInfo(T info) {
this.info = info;
}
public T getInfo() {
return info;
}
}
public class Test {
public static void main(String[] args) throws Exception {
Message<String> msg = new Message<String>(); // Integer是Number的子類
msg.setInfo("Hello World .");
print(msg); // 引用傳遞
}
// 只能是String的泛型或者是Object的泛型使用
public static void print(Message<? super String> temp) {
System.out.println(temp.getInfo()); // 只是輸出
}
}
執行結果:
Hello World .