Java第九篇反射和異常處理
上一篇已經介紹了C#類和java類的區別,由於工作原因經過三天的間斷整理才合完,以備後面複習時對比。這裡把發射單獨做一篇講解。
首先;當程式要使用某個類時,如果該類還未被載入到記憶體,則系統通過載入(通過類載入器完成),連線,初始化實現對這個類的載入。載入:就是指將class檔案讀入記憶體,併為之建立一個Class物件,任何類被使用時都會建立一個Class物件。連線:驗證:是否有正確的內部結構,並和其他類協調一致,準備:負責為類的靜態成員分配記憶體,並設定預設初始化設定,解析:將類的二進位制資料中的符號引用替換為直接引用。初始化就是建立物件的過程。
類載入器:1.根類載入器:BootstrapClassLoader 負責java核心類的載入 2.擴充套件載入器:ExtensionClassLoader :擴充套件目錄中的jar包載入。3.系統類載入器 :systemClassLoader 負責 載入自己的。
通過java反射機制,可以在程式中訪問已經裝載到JVM中的Java物件的描述,實現訪問(私有也可以),檢測,和修改描述Java物件本身資訊的功能,Java反射機制的功能十分強大,在Java。lang。reflect包中提供了對該功能的支援。所有的類都繼承了object類,在object類中定義了getclass方法,該方法返回一個型別為Class的物件。格式:Class 物件名 = 物件。getClass(),得到的物件名可以訪問很多方法,實質是通過Class物件獲取欄位,構造方法,方法,等物件,再通過物件呼叫此結構。
注意:一個類的不同物件通過getclass方法獲取的Class物件是一樣的。因為獲取的是位元組碼檔案的物件。
獲取Class檔案物件的幾種方式:
1.Object類的getClass()方法,需要物件呼叫 2.資料型別的靜態屬性class 3.Class類的靜態方法forName(引數為帶有包名的類全名,比如:ReflectD.Person) 開發一般用第三種。
舉例說明:訪問構造方法,將返回Constructor型別的物件或陣列,每個Constructor物件代表一個構造方法,利用Constructor物件可以操作相應的構造方法。,如訪問指定的構造方法,需要根據該構造方法的入口引數的型別來訪問,例如訪問一個入口引數型別依次為String 和int型的構造方法,1.物件名。getDeclaredConstructor(String.class, int.Class)2.物件名。getDeclaredConstructor(new Class [] {String.class, int.Class}),getConstructor(
利用java。lang。reflect。modifier 類可以解析出getModifiers()方法返回的值所表示的修飾符的資訊,該功能可以檢視該建構函式被什麼修飾符修飾。
同理Field中常用的方法:set方法中的obj為通過構造方法返回的目標類物件,訪問私有時跟構造方法一樣,依然要暴力訪問,即用帶Declared的方法,並且欄位物件呼叫SetAccessible(true)方法。
Method類中常用的方法 invoke為執行方法,obj為目標類物件。
反射有三種實現方法:
第一種方法,forName()靜態方法:Class.forName(String str);
// 注意:str - 所需類的完全限定名。
Class class1 = Class.forName("com.mzsds.fanshe.fanshe");
// 第二種方法,.class
Class class2 = String.class;
// 第三種方法,.getClass();
fanshe fs = new fanshe();
Class class3 = fs.getClass();
最後:利用反射可以呼叫類的私有方法,也可以呼叫父類的私有方法,當呼叫父類的私有方法時需要特殊處理,因為可能獲取不到,以下為轉載的連結
注意呼叫私有成員前要 constructor/Field/method(各種物件).setAccessible(true);
通過反射可以繞過泛型檢查,比如ArrayList<String>的add方法新增int。
ArrayList<Integer> arr = new ArrayList<Integer>();
Class c = arr.getClass();
Method m = c.getMethod("add", Object.class);
m.invoke(arr, "hello");
m.invoke(arr, "world");
m.invoke(arr, "java");
System.out.println(arr);
Proxy類是一個代理類 需要自己實現一個InvocationHandler的自定義類。自定義類實現invoke方法,Proxy類呼叫newProxyInstance方法。類似中介設計模式。
異常處理
Throwable類是所有異常類的超類,該類的兩個直接子類是Error和Exception。其中Error及子類用於指示合理的應用程式不應該試圖捕獲的嚴重問題,Expection及其子類給出了合理應用程式需要捕獲的異常。而expection又分為編譯期異常和執行時異常。runtimeExpection及其子類是執行時異常(可以不處理),其他為編譯器異常(必須處理)。
可控式異常:
IOExpection:當發生某種I/0異常時,丟擲此異常
SQLExpection:提供關於資料庫訪問錯誤的其他錯誤資訊異常
ClassNotFoundExpection:沒找到類的異常
NoSuchFieldExpection:類不包含指定的欄位
NoSuchMethodExpection:無法找到某一特定方法,丟擲該異常。
執行時異常:
IndexOutofBoundsExpection:陣列或者集合超出界限,
NullPointerExpection:訪問物件為null時的異常
ArithmeticExpection:異常的運算條件時的異常,
IllegalArgumentExpection:表明向方法傳遞了一個不合法或者不正確的引數
ClassCastExpection:當試圖講物件轉換為不是例項的子類時丟擲該異常。
獲取異常資訊:
getlocalizedMessage()返回Throwable的本地化描述,
getMessage()獲取此throwable的詳細資訊字串 ,
printStackTrace()將此Throwable及其棧蹤跡輸出至標準錯誤流
tostring()獲取此throwable的剪短描述
在一個方法的執行過程中,如果發生了異常,則Java虛擬機器生成一個代表該異常的物件(它包含了異常的詳細資訊),並把它交給執行時系統,執行時系統尋找相應的程式碼來處理這一異常。我們把生成異常物件並把它提交給執行時系統的過程稱為丟擲(throw)一個異常。
異常處理方法A:有些時候我們處理不了異常,我們就用throws丟擲異常, 格式:放在方法後面 throws 異常類名。丟擲後誰呼叫 誰需要用try cath 處理 或者繼續往上拋。這裡指的是編譯時異常,如果丟擲的是執行時異常,則不用管。
throws與throw 的區別:
throws
用在方法聲明後面,跟的是異常類名,可以跟很多個異常類名,用逗號隔開, 表示丟擲異常,由該方法呼叫者處理,throws表示出現特長的一種可能性,並不一定發生這些異常
throw
用在方法體內,跟的是異常物件名, 只能丟擲一個異常物件名, 表示丟擲異常,由方法體內的語句處理,執行throw一定丟擲了某種異常。
異常處理方法B:執行時系統尋找相應的程式碼來處理這一異常,系統在方法的呼叫棧中查詢,從產生異常的方法開始進行回朔,沿著被呼叫的順序往前尋找,直到找到包含相應異常處理的方法為止。這一過程稱為捕獲(catch)一個異常
格式:
try{
正常程式段,可能丟擲異常;
}
catch (異常類1 異常變數) {
捕捉異常類1有關的處理程式段;
}
catch (異常類2 異常變數) {
捕捉異常類2有關的處理程式段;
}
……
finally{
一定會執行的程式程式碼;
}
l try塊——捕獲異常:用於監控可能發生異常的程式程式碼塊是否發生異常,如果發生異常,Try程式碼塊將丟擲異常類所產生的物件並立刻結束執行,而轉向異常處理catch部分。(try裡的程式碼越少越好)。一旦匹配成功就進行catch處理,然後不再繼續執行try裡的語句,而繼續執行try外面的語句。
對於系統產生的異常或程式塊中未用try監控所產生的一場,將一律由java 編譯系統自動將異常物件丟擲。
l catch塊——處理異常 :丟擲的異常物件如果屬於catch內所定義的異常類,則catch會捕獲該異常,並進入catch中的對應程式碼段繼續執行程式,如果異常物件不屬於catch中所定義的異常類,則進入finally塊繼續執行程式。(catch裡必須有內容。catch裡的異常型別儘量明確,不明確時用Exception)。jdk7的新特性,可以在catch(異常型別1 物件名 | 異常型別2 物件名| ...)同時處理多個處理方式一樣的異常,但是多個異常間必須是平級關係。
Catch包括兩個引數:一個是類名,指出捕獲的異常型別,必須使Throwable類的子類;一個是引數名,用來引用被捕獲的物件。Catch塊所捕獲的物件並不需要與它的引數型別精確匹配,它可以捕獲引數中指出的異常類的物件及其所有子類的物件
l finally塊——最終處理:無論是否發生異常都會執行的語句塊。比如執行關閉開啟的檔案、刪除臨時檔案,關閉資料庫連線等操作。如果在finally塊之前jvm退出了就不能執行了。即使catch和try語句塊裡有return, finally中的最後也會執行。
final:最終,修飾類(不能被繼承),成員變數(常量),方法(不能被重寫),finally:異常的最終處理,finalize:object的方法,用於垃圾回收。
注意:
l 一個try、catch、finally塊之間不能插入任何其它程式碼
l catch可以有多個,try和finally只能有一個
l try後面必須要跟catch、finally其中的一個,即但一個try、catch、finally語句只能省略catch、finally中的一個。
定義多個catch可精確地定位java異常。如果為子類的異常定義了特殊的catch塊,而父類的異常則放在另外一個catch塊中,此時,必須滿足以下規則:子類異常的處理塊必須在父類異常處理塊的前面,否則會發生編譯錯誤。所以,越特殊的異常越在前面處理,越普遍的異常越在後面處理。這類似於 制訂防火牆的規則次序:較特殊的規則在前,較普通的規則在後。
異常類常用方法
常用非法 |
用途 |
Void String getMessage() |
返回異常物件的一個簡短描述 |
Void String toString() |
獲取異常物件的詳細資訊 |
Void printStackTrace() |
在控制檯上列印異常物件和它的追蹤資訊 |
異常的丟擲可以分為兩大類:
l 系統自動丟擲異常
比如上面的例子就是系統自動丟擲異常,通過try catch捕獲異常物件,並繼續相應的處理。
l 通過關鍵字throw將異常物件顯性地丟擲。
即在程式中生成自己的異常物件,即異常可以不是出錯產生,而是人為編寫程式碼主動丟擲。顯性丟擲異常從某種程度上實現了將處理異常的程式碼從正常流程程式碼中分離開了,使得程式的主線保證相對完整,同時增加了程式的可讀性和可維護性。異常沿著呼叫層次向上丟擲,交由呼叫它的方法來處理。
為什麼要在方法中丟擲異常?
系統自動丟擲異常一般就能解決大部分問題,但有時候,程式會產生特定的要求,需要由使用者自己定義異常資訊,又或者聯合開發程式模組時,不同程式設計師需要將各自負責程式碼部分儘量避免因程式出錯影響其他人的編碼,都需要顯式丟擲異常,以便程式進行處理。這時候就需要在方法中丟擲異常。
異常丟擲的語法:
throw new 異常類( );
其中異常類必須Throwable類及其子類。
throws子句的方法宣告的一般格式如下:
<型別說明> 方法名(引數列表) throws <異常型別列表>
{
方法體;
}
舉例:
class ThrowException{
// throwOne方法後用throws宣告異常類ArithmeticException
static void throwOne(int i) throws ArithmeticException {
if(i==0)
throw new ArithmeticException("i值為零"); //用throw丟擲一個異常
}
public static void main(String args[]){
//捕獲異常
try{
throwOne(0);
}
catch(ArithmeticException e){
System.out.println("已捕獲到異常錯誤: "+e.getMessage());
}
}
}
程式執行結果:已捕獲到異常錯誤: i值為零
四、自定義異常
使用者自定義的異常類,只需繼承一個已有的異常類就可以了,包括繼承Execption類及其子類,或者繼承已自定義好的異常類。如果沒有特別說明,可以直接用Execption類作為父類。
自定義類的格式如下:
class 異常類名 extends Exception
{
……
}
1.自定義異常類必須繼承自Throwable或Exception類,建議用Exception類。一般不把自定義異常作為Error的子類,因為Error通常被用來表示系統內部的嚴重故障。
2. 當自定義異常是從RuntimeException及其子類繼承而來時,該自定義異常是執行時異常,程式中可以不捕獲和處理它。
3. 當自定義異常是從Throwable、Exception及其子類繼承而來時,該自定義異常是編譯時異常,也即程式中必須捕獲並處理它。
使用自定義異常的步驟如下:
l 首先通過繼承java.lang.Exception類宣告自定義的異常類。
l 在方法的宣告部分用throws語句宣告該方法可能丟擲的異常。
l 在方法體的適當位置建立自定義異常類的物件,並用throw語句將異常丟擲。
l 呼叫該方法時對可能產生的異常進行捕獲,並處理異常。
//NewException.java
class NumberRangeException extends Exception{
public NumberRangeException(String msg){
super(msg);
}
//throws重新丟擲異常NumberRangeException
public int CalcAnswer(String str1,String str2) throws NumberRangeException{
int int1, int2;
int answer = -1;
try {
int1 = Integer.parseInt(str1); //可能產生異常物件NumberFormatException e
int2 = Integer.parseInt(str2); //可能產生異常物件NumberFormatException e
if( (int1 < 10) || (int1 > 20) || (int2< 10) || (int2 > 20) ){
NumberRangeException e = new NumberRangeException("Numbersare not within the specified range.");
throw e; //丟擲自定義異常物件NumberRangeExceptione
}
answer = int1 + int2;
}
catch (NumberFormatExceptione){ //捕獲異常物件NumberRangeException e
System.out.println( e.toString() );
}
return answer;
}
//在呼叫方法getAnswer中捕獲異常
public void getAnswer(){
String answerStr;
try{
//將num1、num2的中的數字更改為小於10或大於20,以檢視捕獲異常結果。
Stringnum1="13";
Stringnum2="12";
int answer = CalcAnswer(num1, num2); //丟擲異常物件NumberRangeException e
answerStr = String.valueOf(answer);
}
catch (NumberRangeExceptione){ //捕獲異常物件NumberRangeException e
answerStr = e.getMessage();
}
System.out.println(answerStr);
}
public static void main(String args[]) {
NumberRangeException t1=new NumberRangeException("test");
t1.getAnswer();
}
注意事項:1.子類重寫父類方法時,子類方法必須丟擲相同的異常或者父類異常的子類。2.如果父類丟擲多個異常,子類重寫父類時,只能丟擲相同異常或者它的子類(可以少,不能多), 子類不能丟擲父類沒有的異常。3.如果被重寫的方法在父類中沒有異常丟擲,那麼子類的方法絕不可以丟擲異常,如果子類方法中有異常發生,那麼子類只能try,不能throws。