泛型是Java最具影響力的新特性之一,Java程序員需要深入理解這一特性。
|-- 從字面上看:泛型就是泛泛的指定對象所操作的類型,而不像常規方式一樣使用某種固定的類型去指定。
|-- 從本質上看:泛型就是參數化類型,在創建類、接口、方法時可以用類型參數指定他們所要操作的數據類型。
|-- 類型參數最終都是要被實現的。
/**一個泛型類的簡單實例**/
public class Gen<T> { //這裏的T是類型參數的名稱,又叫標記,是實際類型的佔位符
private T ob;
Gen(T ob){
this.ob = ob;
}
public T getOb(){
return ob;
}
public T setOb(T ob){
this.ob = ob;
return this.ob;
}
/*
* 因爲Object是所有類的基類,所以這裏可以使用getClass()和getName()。
* 如果使用到其他類的方法,是不行的。因爲編譯器並不知道T代表的具體類型。
* 如果要想使用其他類的方法,T要限定範圍,也就是有界類型。
*
* */
public void showtype(){
System.out.println("ob的類型是:"+ob.getClass().getName());
}
}
/**下面是一個泛型類不合理的實現方式**/
public class GenDemo {
public static void main(String[] args) {
//Gen<Integer>將<Integer>傳遞給力Gen<T>的T。因爲是在類上聲明的所以對整個類都有效。
Gen<Integer> Ob1 = new Gen(new Integer(123));
Integer i = Ob1.getOb();
System.out.println(i);
Ob1.showtype();
//Gen沒有指定要傳遞的數據類型,編譯後會被Object替換。new Gen<String>只是在創建Gen實例時指定Gen副本的T。
7 //這時構造函數相當於 Object ob = "123456"。Jdk7以後創建實例好像已經不用重新聲明瞭、
Gen ob2 = new Gen<String>("123456");
Object s = ob2.getOb();
System.out.println(s);
ob2.showtype();//java.lang.String。getClass()獲取的是創建"123456"這個對象的類。
//ob1=ob2;/**同一種泛型可以對應多個版本(因爲參數類型是不確定的),但是不同版本的泛型類實例是不兼容的。**/
}
}
需要注意的是至始至終只有一個 Gen 類。Java編譯器實際上只是擦除了所有泛型類信息,將之替換成必須的類型。
/**一個加深對泛型理解的例子**/
public class Tool{
public <T> void add(Collection<? super T> a,T b){
// <?>可以引用各種參數化的類型,可以調用與參數無關的方法,不能調用與參數有關的方法
// a.addAll(a);// 編譯失敗。 boolean add(E e)
a.remove(b);// boolean remove(Object o)
}
}
public class Test {
public static void main(String[] args) {
Tool tool =new Tool();
//不完全確定list的類型。至少可以是Number,不管是什麼類型,完全可以裝下Integer
List<? super Number> list = new ArrayList<Number>();
//完全不確定list的類型。如果是Long就裝不下,Integer
// List<? extends Number> list = new ArrayList<Number>();//編譯失敗
list.add(new Integer(123));
tool.add(list,123);
}
}
從上面可以看出泛型的一些使用規則:
|-- 泛型的類型參數只能是類類型(包括自定義類),不能是基礎數據類型。
|-- 同一種泛型可以對應多個版本(因爲參數類型是不確定的),但是不同版本的泛型類實例是不兼容的。
|-- 泛型可以是靜態的也可以是非靜態的,靜態內不能使用類型參數。靜態方法的返回類型不能是類型參數
|-- 不能創建泛型異常類。
|-- 類型參數不能被實例化。
使用泛型的好處:
|-- 創建類型安全的代碼,將運行時錯誤轉換成編譯時錯誤。
|-- 簡化了處理過程,不再需要顯示的使用 強制類型轉換,所有類型轉換都是自動、隱式進行的。
|-- 擴展了代碼的重用能力。
泛型的語法:
|-- 聲明泛型類:class class_name<type_param_list>
|-- 引用的語法:class<type_arg_list> var_name = newclass<type_arg_list>(cons_arg_list);
泛型的有界類型:
|-- 告訴編譯器類型參數的範圍。我們在實例化對象時所傳遞的類型也必須在這個範圍之內。
|-- 限定上界 : <T extends Number>範圍:Number及其子類。
|-- 限定下界 : <T super Number> 範圍:Number及其父類。
|-- 限制方法和可以操作對象的類型。
|-- 在泛型中可以使用通配符參數,用?來指定。表示未知類型
|-- 解決了指定佔位符操作數據類型的侷限性。可以同時操作多種數據類型。
|-- <T extends Number> 和 <?extends Number> 的區別。從範圍來看他們是相等的。但是。。。
|-- 通配符是不能被用做最終類型的,類型參數最終都是要被實現的。
/** 一個簡單的有界類 **/
/*
* 一個簡單的計算數組和的類
* 實現:
* 要操作的數據類型很多,想到 泛型。
* 所有數字類型都是Number的子類。
* 在計算時要把對象轉成基本數據類型進行運算。
* 所以要限定標記的範圍,告訴編譯器我們要使用的數據類型都是Number的子類。
*
* */
// 只有同一種數據類型纔可以比較 T
public class Stats<T extends Number> {
private T[] nums;
Stats(T[] n) {
nums = n;
}
T[] get() {
return nums;
}
/*
*判斷兩個數組的大小
*
*這裏的Stats<?>是函數的參數,是爲了更安全的表述ob的類型。
*
*/
public double compareSum(Stats<?> ob) {
return add() - ob.add();
}
public double add() {
double sum = 0.0;
for (int i = 0; i < nums.length; i++) {
sum += nums[i].doubleValue();
}
return sum;
}
}
public static void main(String[] args) {
Integer[] nums = {2,3,1,5,6,3,4};
// Integer nums[] = {2,3,1,5,6,3,4,10};//數組的另一種表現形式。
Number[] nums2 = {1.1,2.1,3.1,4};
Stats<Integer> stats = new Stats<Integer>(nums);
Stats<Number> stats2 = new Stats<Number>(nums2);
double sum = stats.add();
double sum2 = stats2.add();
System.out.println(sum + "--" +sum2);
System.out.println(stats.compareSum(stats2));
}
泛型方法:
|-- 可以將類方法泛型化,即使他們的類不是泛型類。
|-- 泛型方法可以指定參數類型,返回值類型,參數之間的關係。
/*
* 用泛型方法判斷任意一個數是否在一個數組中
*
*/
public class GenFunc {
public static <T, Y extends T> boolean isIn(T t, Y[] y) {
for (int i = 0; i < y.length; i++) {
if (t.equals(y[i])) return true;
}
return false;
}
public static void main(String[] args) {
Integer[] nums = {21,32,545,87} ;
int i = 21;
if(isIn(i,nums)) System.out.println(i + "在nums中");
String[] strs = {"123","456","789"} ;
String s = "123";
if(isIn(s,strs)) System.out.println(s + "在nums中");
}
}
|-- 類型參數在方法返回前聲明
|-- 泛型可以是靜態的也可以是非靜態的,靜態方法不能使用定義在類上的類型參數。只能定義在方法上、
|-- <T, Y extends T> 限定了 Y 的範圍,保證了 T 和 Y 是同種數據類型。(強制類型安全)
泛型接口:
|-- 泛型接口可以針對不同類型的數據進行實現。。。
|-- 一般而言如果實現了泛型接口,那麼類也必須泛型化,至少應該實現接口的類型參數
|-- 子類都必須向上傳遞超類所需要的類型參數。
/*
* GenInterface功能擴展。
* 希望GenInterface可以實現計算出對象數組的最值功能。
* 想到了接口
* 對象比較想到Comparable
* Comparable<T>的子類都具有比較功能。
* 兩個對象比較,必須數據類型一致,並且是Comparable<T>的子類纔可以比較
*
* */
/***
* 比較大小要返回值,返回值類型不確定。 泛型。
* 只有同一種數據類型纔可以比較 T
* 創建接口的時候最好限定類型範圍,這符合邏輯思維
****/
interface minMax<T extends Comparable<T>> {
T min(T[] vals);
T max(T[] vals);
}
/***********
*
* 子類必須現實接口的類型 minMax<T>,把T傳遞個接口
* 不實現默認Object 泛型就沒意義了
* 如果你覺得這裏有點牽強,只要保證子類在範圍內不就行了。那就錯了。
* 如果沒有把類型傳遞給接口:那麼就相當於
* 接口的函數是:Object min(Object vals)
* GenInterface:T min(T[] vals) 這是不行的。。。
*
******/
public class GenInterface<T extends Comparable<T>> implements minMax<T> {
public T min(T[] vals) {
T min = vals[0];
for (int i = 1; i < vals.length; i++)
if (min.compareTo(vals[i]) > 0) min = vals[i];
return min;
}
public T max(T[] vals) {
T max = vals[0];
for (int i = 1; i < vals.length; i++)
if (max.compareTo(vals[i]) < 0) max = vals[i];
return max;
}
public static void main(String[] args) {
String[] vals = {"asdas","bsdas","esdasd","ffasd"};
GenInterface<String> demo1 = new GenInterface<String>();
String min = demo1.min(vals);
String max = demo1.max(vals);
System.out.println(min +"--"+ max);
}
}
泛型類實例的強制類型轉換:
|-- 只有當泛型類實例的類型相互兼容,類型參數也相同的時候纔可以。
|-- 但是這樣轉換有意義?
|-- GenInterface<String> demo1 = new GenInterface<String>();
|-- GenInterface<Integen> demo2 = new GenInterface<Integen>();
|-- 雖然都GenInterface類型實例,但是已經是兩種完全不兼容的對象了、這是泛型提高代碼重用帶來的、
泛型工作的原理:
|-- 爲了兼容以前的老版本,java使用擦拭實現泛型。怎麼擦拭
|-- 使用具體類型替換類型參數,如果沒有顯示的指定,就使用Object。