Java中常見的幾種RuntimeException
總結了一下JAVA中常見的幾種RuntimeException,大約有如下幾種:
NullPointerException - 空指標引用異常
ClassCastException - 型別強制轉換異常。
IllegalArgumentException - 傳遞非法引數異常。
ArithmeticException - 算術運算異常
ArrayStoreException - 向陣列中存放與宣告型別不相容物件異常
IndexOutOfBoundsException - 下標越界異常
NegativeArraySizeException - 建立一個大小為負數的陣列錯誤異常
NumberFormatException - 數字格式異常
SecurityException - 安全異常
UnsupportedOperationException - 不支援的操作異常
常見的RuntimeException- -
RuntimeException是開發中最容易遇到的,下面列舉一下常見的RuntimeException:
1、NullPointerException:見的最多了,其實很簡單,一般都是在null物件上呼叫方法了。
String s=null;
boolean eq=s.equals(""); // NullPointerException
這裡你看的非常明白了,為什麼一到程式中就暈呢?
public int getNumber(String str){
if(str.equals("A")) return 1;
else if(str.equals("B")) return 2;
}
這個方法就有可能丟擲NullPointerException,我建議你主動丟擲異常,因為程式碼一多,你可能又暈了。
public int getNumber(String str){
if(str==null) throw new NullPointerException("引數不能為空");
//你是否覺得明白多了
if(str.equals("A")) return 1;
else if(str.equals("B")) return 2;
}
2、NumberFormatException:繼承IllegalArgumentException,字串轉換為數字時出現。比如int i= Integer.parseInt("ab3");
3、ArrayIndexOutOfBoundsException:陣列越界。比如 int[] a=new int[3]; int b=a[3];
4、StringIndexOutOfBoundsException:字串越界。比如 String s="hello"; char c=s.chatAt(6);
5、ClassCastException:型別轉換錯誤。比如 Object obj=new Object(); String s=(String)obj;
6、UnsupportedOperationException:該操作不被支援。如果我們希望不支援這個方法,可以丟擲這個異常。既然不支援還要這個幹嗎?有可能子類中不想支援父類中有的方法,可以直接丟擲這個異常。
7、ArithmeticException:算術錯誤,典型的就是0作為除數的時候。
8、IllegalArgumentException:非法引數,在把字串轉換成數字的時候經常出現的一個異常,我們可以在自己的程式中好好利用這個異常。
我們可建立一個控制器,令其捕獲所有型別的違例。具體的做法是捕獲基礎類違例型別Exception(也存在其他型別的基礎違例,但Exception是適用於幾乎所有程式設計活動的基礎)。如下所示:
catch(Exception e) {
System.out.println("caught an exception");
}
這段程式碼能捕獲任何違例,所以在實際使用時最好將其置於控制器列表的末尾,防止跟隨在後面的任何特殊違例控制器失效。
對於程式設計師常用的所有違例類來說,由於Exception類是它們的基礎,所以我們不會獲得關於違例太多的資訊,但可呼叫來自它的基礎類Throwable的方法:
String getMessage()
獲得詳細的訊息。
String toString()
返回對Throwable的一段簡要說明,其中包括詳細的訊息(如果有的話)。
void printStackTrace()
void printStackTrace(PrintStream)
打印出Throwable和Throwable的呼叫堆疊路徑。呼叫堆疊顯示出將我們帶到違例發生地點的方法呼叫的順序。
第一個版本會打印出標準錯誤,第二個則打印出我們的選擇流程。若在Windows下工作,就不能重定向標準錯誤。因此,我們一般願意使用第二個版本,並將結果送給System.out;這樣一來,輸出就可重定向到我們希望的任何路徑。
除此以外,我們還可從Throwable的基礎類Object(所有物件的基礎型別)獲得另外一些方法。對於違例控制來說,其中一個可能有用的是getClass(),它的作用是返回一個物件,用它代表這個物件的類。我們可依次用getName()或toString()查詢這個Class類的名字。亦可對Class物件進行一些複雜的操作,儘管那些操作在違例控制中是不必要的。本章稍後還會詳細講述Class物件。
下面是一個特殊的例子,它展示了Exception方法的使用(若執行該程式遇到困難,請參考第3章3.1.2小節“賦值”):
//: ExceptionMethods.Java
// Demonstrating the Exception Methods
package c09;
public class ExceptionMethods {
public static void main(String[] args) {
try {
throw new Exception("Here's my Exception");
} catch(Exception e) {
System.out.println("Caught Exception");
System.out.println(
"e.getMessage(): " + e.getMessage());
System.out.println(
"e.toString(): " + e.toString());
System.out.println("e.printStackTrace():");
e.printStackTrace();
}
}
} ///:~
該程式輸出如下:
Caught Exception
e.getMessage(): Here's my Exception
e.toString(): java.lang.Exception: Here's my Exception
e.printStackTrace():
java.lang.Exception: Here's my Exception
at ExceptionMethods.main
可以看到,該方法連續提供了大量資訊——每類資訊都是前一類資訊的一個子集。
本章的第一個例子是:
if(t == null)
throw new NullPointerException();
看起來似乎在傳遞進入一個方法的每個控制代碼中都必須檢查null(因為不知道呼叫者是否已傳遞了一個有效的控制代碼),這無疑是相當可怕的。但幸運的是,我們根本不必這樣做——它屬於Java進行的標準執行期檢查的一部分。若對一個空控制代碼發出了呼叫,Java會自動產生一個NullPointerException違例。所以上述程式碼在任何情況下都是多餘的。
這個類別裡含有一系列違例型別。它們全部由Java自動生成,毋需我們親自動手把它們包含到自己的違例規範裡。最方便的是,通過將它們置入單獨一個名為RuntimeException的基礎類下面,它們全部組合到一起。這是一個很好的繼承例子:它建立了一系列具有某種共通性的型別,都具有某些共通的特徵與行為。此外,我們沒必要專門寫一個違例規範,指出一個方法可能會“擲”出一個RuntimeException,因為已經假定可能出現那種情況。由於它們用於指出程式設計中的錯誤,所以幾乎永遠不必專門捕獲一個“執行期違例”——RuntimeException——它在預設情況下會自動得到處理。若必須檢查RuntimeException,我們的程式碼就會變得相當繁複。在我們自己的包裡,可選擇“擲”出一部分RuntimeException。
如果不捕獲這些違例,又會出現什麼情況呢?由於編譯器並不強制違例規範捕獲它們,所以假如不捕獲的話,一個RuntimeException可能過濾掉我們到達main()方法的所有途徑。為體會此時發生的事情,請試試下面這個例子:
//: NeverCaught.java
// Ignoring RuntimeExceptions
public class NeverCaught {
static void f() {
throw new RuntimeException("From f()");
}
static void g() {
f();
}
public static void main(String[] args) {
g();
}
} ///:~
大家已經看到,一個RuntimeException(或者從它繼承的任何東西)屬於一種特殊情況,因為編譯器不要求為這些型別指定違例規範。
輸出如下:
java.lang.RuntimeException: From f()
at NeverCaught.f(NeverCaught.java:9)
at NeverCaught.g(NeverCaught.java:12)
at NeverCaught.main(NeverCaught.java:15)
所以答案就是:假若一個RuntimeException獲得到達main()的所有途徑,同時不被捕獲,那麼當程式退出時,會為那個違例呼叫printStackTrace()。
注意也許能在自己的程式碼中僅忽略RuntimeException,因為編譯器已正確實行了其他所有控制。因為RuntimeException在此時代表一個程式設計錯誤:
(1) 一個我們不能捕獲的錯誤(例如,由客戶程式設計師接收傳遞給自己方法的一個空控制代碼)。
(2) 作為一名程式設計師,一個應在自己的程式碼中檢查的錯誤(如ArrayIndexOutOfBoundException,此時應注意陣列的大小)。
可以看出,最好的做法是在這種情況下違例,因為它們有助於程式的除錯。
另外一個有趣的地方是,我們不可將Java違例劃分為單一用途的工具。的確,它們設計用於控制那些討厭的執行期錯誤——由程式碼控制範圍之外的其他力量產生。但是,它也特別有助於除錯某些特殊型別的程式設計錯誤,那些是編譯器偵測不到的。
覆蓋一個方法時,只能產生已在方法的基礎類版本中定義的違例。這是一個重要的限制,因為它意味著與基礎類協同工作的程式碼也會自動應用於從基礎類衍生的任何物件(當然,這屬於基本的OOP概念),其中包括違例。
下面這個例子演示了強加在違例身上的限制類型(在編譯期):
//: StormyInning.Java
// Overridden methods may throw only the
// exceptions specified in their base-class
// versions, or exceptions derived from the
// base-class exceptions.
class BaseballException extends Exception {}
class Foul extends BaseballException {}
class Strike extends BaseballException {}
abstract class Inning {
Inning() throws BaseballException {}
void event () throws BaseballException {
// Doesn't actually have to throw anything
}
abstract void atBat() throws Strike, Foul;
void walk() {} // Throws nothing
}
class StormException extends Exception {}
class RainedOut extends StormException {}
class PopFoul extends Foul {}
interface Storm {
void event() throws RainedOut;
void rainHard() throws RainedOut;
}
public class StormyInning extends Inning
implements Storm {
// OK to add new exceptions for constrUCtors,
// but you must deal with the base constructor
// exceptions:
StormyInning() throws RainedOut,
BaseballException {}
StormyInning(String s) throws Foul,
BaseballException {}
// Regular methods must conform to base class:
//! void walk() throws PopFoul {} //Compile error
// Interface CANNOT add exceptions to existing
// methods from the base class:
//! public void event() throws RainedOut {}
// If the method doesn't already exist in the
// base class, the exception is OK:
public void rainHard() throws RainedOut {}
// You can choose to not throw any exceptions,
// even if base version does:
public void event() {}
// Overridden methods can throw
// inherited exceptions:
void atBat() throws PopFoul {}
public static void main(String[] args) {
try {
StormyInning si = new StormyInning();
si.atBat();
} catch(PopFoul e) {
} catch(RainedOut e) {
} catch(BaseballException e) {}
// Strike not thrown in derived version.
try {
// What happens if you upcast?
Inning i = new StormyInning();
i.atBat();
// You must catch the exceptions from the
// base-class version of the method:
} catch(Strike e) {
} catch(Foul e) {
} catch(RainedOut e) {
} catch(BaseballException e) {}
}
} ///:~
在Inning中,可以看到無論構建器還是event()方法都指出自己會“擲”出一個違例,但它們實際上沒有那樣做。這是合法的,因為它允許我們強迫使用者捕獲可能在覆蓋過的event()版本里新增的任何違例。同樣的道理也適用於abstract方法,就象在atBat()裡展示的那樣。
“interface Storm”非常有趣,因為它包含了在Incoming中定義的一個方法——event(),以及不是在其中定義的一個方法。這兩個方法都會“擲”出一個新的違例型別:RainedOut。當執行到“StormyInning extends”和“implements Storm”的時候,可以看到Storm中的event()方法不能改變Inning中的event()的違例介面。同樣地,這種設計是十分合理的;否則的話,當我們操作基礎類時,便根本無法知道自己捕獲的是否正確的東西。當然,假如interface中定義的一個方法不在基礎類裡,比如rainHard(),它產生違例時就沒什麼問題。
對違例的限制並不適用於構建器。在StormyInning中,我們可看到一個構建器能夠“擲”出它希望的任何東西,無論基礎類構建器“擲”出什麼。然而,由於必須堅持按某種方式呼叫基礎類構建器(在這裡,會自動呼叫預設構建器),所以衍生類構建器必須在自己的違例規範中宣告所有基礎類構建器違例。
StormyInning.walk()不會編譯的原因是它“擲”出了一個違例,而Inning.walk()卻不會“擲”出。若允許這種情況發生,就可讓自己的程式碼呼叫Inning.walk(),而且它不必控制任何違例。但在以後替換從Inning衍生的一個類的物件時,違例就會“擲”出,造成程式碼執行的中斷。通過強迫衍生類方法遵守基礎類方法的違例規範,物件的替換可保持連貫性。
覆蓋過的event()方法向我們顯示出一個方法的衍生類版本可以不產生任何違例——即便基礎類版本要產生違例。同樣地,這樣做是必要的,因為它不會中斷那些已假定基礎類版本會產生違例的程式碼。差不多的道理亦適用於atBat(),它會“擲”出PopFoul——從Foul衍生出來的一個違例,而Foul違例是由atBat()的基礎類版本產生的。這樣一來,假如有人在自己的程式碼裡操作Inning,同時呼叫了atBat(),就必須捕獲Foul違例。由於PopFoul是從Foul衍生的,所以違例控制器(模組)也會捕獲PopFoul。
最後一個有趣的地方在main()內部。在這個地方,假如我們明確操作一個StormyInning物件,編譯器就會強迫我們只捕獲特定於那個類的違例。但假如我們上溯造型到基礎型別,編譯器就會強迫我們捕獲針對基礎類的違例。通過所有這些限制,違例控制程式碼的“健壯”程度獲得了大幅度改善(註釋③)。
③:ANSI/ISO C++施加了類似的限制,要求衍生方法違例與基礎類方法擲出的違例相同,或者從後者衍生。在這種情況下,C++實際上能夠在編譯期間檢查違例規範。
我們必須認識到這一點:儘管違例規範是由編譯器在繼承期間強行遵守的,但違例規範並不屬於方法型別的一部分,後者僅包括了方法名以及自變數型別。因此,我們不可在違例規範的基礎上覆蓋方法。除此以外,儘管違例規範存在於一個方法的基礎類版本中,但並不表示它必須在方法的衍生類版本中存在。這與方法的“繼承”頗有不同(進行繼承時,基礎類中的方法也必須在衍生類中存在)。換言之,用於一個特定方法的“違例規範介面”可能在繼承和覆蓋時變得更“窄”,但它不會變得更“寬”——這與繼承時的類介面規則是正好相反的。
RuntimeException可以由系統自動丟擲,可以不進行try...catch
但如果有try,則必須有finally,可以沒有catch