1. 程式人生 > >java中的泛型和反射的一些總結

java中的泛型和反射的一些總結

什麼叫反射?

反射是框架設計的靈魂

(使用的前提條件:必須先得到代表的位元組碼的Class,Class類用於表示.class檔案(位元組碼))

一、反射的概述

JAVA反射機制是在執行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個物件,都能夠呼叫它的任意一個方法和屬性;這種動態獲取的資訊以及動態呼叫物件的方法的功能稱為java語言的反射機制。

要想解剖一個類,必須先要獲取到該類的位元組碼檔案物件。而解剖使用的就是Class類中的方法.所以先要獲取到每一個位元組碼檔案對應的Class型別的物件.

以上的總結就是什麼是反射

反射就是把java類中的各種成分對映成一個個的Java物件

例如:一個類有:成員變數、方法、構造方法、包等等資訊,利用反射技術可以對一個類進行解剖,把個個組成部分對映成一個個物件。

     (其實:一個類中這些成員方法、構造方法、在加入類中都有一個類來描述)

如圖是類的正常載入過程:反射的原理在與class物件。

熟悉一下載入的時候:Class物件的由來是將class檔案讀入記憶體,併為之建立一個Class物件。

在java中我們可以通過反射的機制來動態的獲取一個已經定義過的類,其中包括類構造方法的獲取,屬性的獲取,方法的獲取,在java提供的反射機制中可以使用reflect來獲取,具體獲取的方法如下:

import java.lang.reflect.Constructor;//用來獲取建構函式的
import java.lang.reflect.Field; //獲取成員變數
import java.lang.reflect.Method;//獲取定義的方法

什麼叫泛型?為什麼要使用泛型?

泛型,即“引數化型別”。一提到引數,最熟悉的就是定義方法時有形參,然後呼叫此方法時傳遞實參。那麼引數化型別怎麼理解呢?顧名思義,就是將型別由原來的具體的型別引數化,類似於方法中的變數引數,此時型別也定義成引數形式(可以稱之為型別形參),然後在使用/呼叫時傳入具體的型別(型別實參)。

泛型的本質是為了引數化型別(在不建立新的型別的情況下,通過泛型指定的不同型別來控制形參具體限制的型別)。也就是說在泛型使用過程中,操作的資料型別被指定為一個引數,這種引數型別可以用在類、介面和方法中,分別被稱為泛型類、泛型介面、泛型方法。

通過泛型你可以定義泛型類。介面和方法。

幾個注意的點:

特性:

泛型只在編譯階段有效。看下面的程式碼:

List<String> stringArrayList = new ArrayList<String>();
List<Integer> integerArrayList = new ArrayList<Integer>();

Class classStringArrayList = stringArrayList.getClass();
Class classIntegerArrayList = integerArrayList.getClass();

if(classStringArrayList.equals(classIntegerArrayList)){
   Log.d("泛型測試","型別相同");
}

輸出結果:D/泛型測試: 型別相同

通過上面的例子可以證明,在編譯之後程式會採取去泛型化的措施。也就是說Java中的泛型,只在編譯階段有效。在編譯過程中,正確檢驗泛型結果後,會將泛型的相關資訊擦出,並且在物件進入和離開方法的邊界處新增型別檢查和型別轉換的方法。也就是說,泛型資訊不會進入到執行時階段。

對此總結成一句話:泛型型別在邏輯上看以看成是多個不同的型別,實際上都是相同的基本型別。

 泛型萬用字元:

我們知道IngeterNumber的一個子類,同時在特性章節中我們也驗證過Generic<Ingeter>Generic<Number>實際上是相同的一種基本型別。那麼問題來了,在使用Generic<Number>作為形參的方法中,能否使用Generic<Ingeter>的例項傳入呢?在邏輯上類似於Generic<Number>Generic<Ingeter>是否可以看成具有父子關係的泛型型別呢?

為了弄清楚這個問題,我們使用Generic<T>這個泛型類繼續看下面的例子:

public void showKeyValue1(Generic<Number> obj){
   Log.d("泛型測試","key value is " + obj.getKey());
}
Generic<Integer> gInteger = new Generic<Integer>(123);
Generic<Number> gNumber = new Generic<Number>(456);

showKeyValue(gNumber);

// showKeyValue這個方法編譯器會為我們報錯:Generic<java.lang.Integer> 
// cannot be applied to Generic<java.lang.Number>
// showKeyValue(gInteger);

通過提示資訊我們可以看到Generic<Integer>不能被看作為`Generic<Number>的子類。由此可以看出:同一種泛型可以對應多個版本(因為引數型別是不確定的),不同版本的泛型類例項是不相容的。

回到上面的例子,如何解決上面的問題?總不能為了定義一個新的方法來處理Generic<Integer>型別的類,這顯然與java中的多臺理念相違背。因此我們需要一個在邏輯上可以表示同時是Generic<Integer>Generic<Number>父類的引用型別。由此型別萬用字元應運而生。

我們可以將上面的方法改一下:

public void showKeyValue1(Generic<?> obj){
   Log.d("泛型測試","key value is " + obj.getKey());
}

型別萬用字元一般是使用?代替具體的型別實參,注意了,此處’?’是型別實參,而不是型別形參 。重要說三遍!此處’?’是型別實參,而不是型別形參 ! 此處’?’是型別實參,而不是型別形參 !再直白點的意思就是,此處的?和Number、String、Integer一樣都是一種實際的型別,可以把?看成所有型別的父類。是一種真實的型別。

可以解決當具體型別不確定的時候,這個萬用字元就是 ?  ;當操作型別時,不需要使用型別的具體功能時,只使用Object類中的功能。那麼可以用 ? 萬用字元來表未知型別。

泛型上下邊界

在使用泛型的時候,我們還可以為傳入的泛型型別實參進行上下邊界的限制,如:型別實參只准傳入某種型別的父類或某種型別的子類。

為泛型新增上邊界,即傳入的型別實參必須是指定型別的子型別

public void showKeyValue1(Generic<? extends Number> obj){
   Log.d("泛型測試","key value is " + obj.getKey());
}
Generic<String> generic1 = new Generic<String>("11111");
Generic<Integer> generic2 = new Generic<Integer>(2222);
Generic<Float> generic3 = new Generic<Float>(2.4f);
Generic<Double> generic4 = new Generic<Double>(2.56);

//這一行程式碼編譯器會提示錯誤,因為String型別並不是Number型別的子類
//showKeyValue1(generic1);

showKeyValue1(generic2);
showKeyValue1(generic3);
showKeyValue1(generic4);

如果我們把泛型類的定義也改一下:

public class Generic<T extends Number>{
   private T key;

   public Generic(T key) {
       this.key = key;
   }

   public T getKey(){
       return key;
   }
}
//這一行程式碼也會報錯,因為String不是Number的子類
Generic<String> generic1 = new Generic<String>("11111");

再來一個泛型方法的例子:

//在泛型方法中新增上下邊界限制的時候,必須在許可權宣告與返回值之間的<T>上新增上下邊界,即在泛型宣告的時候新增
//public <T> T showKeyName(Generic<T extends Number> container),編譯器會報錯:"Unexpected bound"
public <T extends Number> T showKeyName(Generic<T> container){
   System.out.println("container key :" + container.getKey());
   T test = container.getKey();
   return test;
}

通過上面的兩個例子可以看出:泛型的上下邊界新增,必須與泛型的宣告在一起 。

關於泛型陣列要提一下

看到了很多文章中都會提起泛型陣列,經過檢視sun的說明文件,在java中是”不能建立一個確切的泛型型別的陣列”的。

也就是說下面的這個例子是不可以的:

List<String>[] ls = new ArrayList<String>[10];

而使用萬用字元建立泛型陣列是可以的,如下面這個例子:

List<?>[] ls = new ArrayList<?>[10];

這樣也是可以的:

List<String>[] ls = new ArrayList[10];

下面使用Sun的一篇文件的一個例子來說明這個問題:

List<String>[] lsa = new List<String>[10]; // Not really allowed.    
Object o = lsa;    
Object[] oa = (Object[]) o;    
List<Integer> li = new ArrayList<Integer>();    
li.add(new Integer(3));    
oa[1] = li; // Unsound, but passes run time store check    
String s = lsa[1].get(0); // Run-time error: ClassCastException.

這種情況下,由於JVM泛型的擦除機制,在執行時JVM是不知道泛型資訊的,所以可以給oa[1]賦上一個ArrayList而不會出現異常,但是在取出資料的時候卻要做一次型別轉換,所以就會出現ClassCastException,如果可以進行泛型陣列的宣告,上面說的這種情況在編譯期將不會出現任何的警告和錯誤,只有在執行時才會出錯。

而對泛型陣列的宣告進行限制,對於這樣的情況,可以在編譯期提示程式碼有型別安全問題,比沒有任何提示要強很多。