異常
什么是異常?Java代碼在運行時期發生的問題就是異常。
在Java中,把異常信息封裝成了一個類。當出現了問題時,就會創建異常類對象並拋出異常相關的信息(如異常出現的位置、原因等)。
異常的繼承體系
在Java中使用Exception類來描述異常。
查看API中Exception的描述,Exception 類及其子類是 Throwable 的一種形式,它用來表示java程序中可能會產生的異常,並要求對產生的異常進行合理的異常處理。
繼續觀察,我們可以發現Exception有繼承關系,它的父類是Throwable。Throwable是Java 語言中所有錯誤或異常的超類,即祖宗類。
另外,在異常Exception類中,有一個子類要特殊說明一下,RuntimeException子類,RuntimeException及其它的子類只能在Java程序運行過程中出現。
我們再來觀察Throwable類,能夠發現與異常Exception平級的有一個Error,它是Throwable的子類,它用來表示java程序中可能會產生的嚴重錯誤。解決辦法只有一個,修改代碼避免Error錯誤的產生。
異常繼承體系總結:
Throwable: 它是所有錯誤與異常的超類(祖宗類)
|- Error 錯誤
|- Exception 編譯期異常,進行編譯JAVA程序時出現的問題
|- RuntimeException 運行期異常, JAVA程序運行過程中出現的問題
異常與錯誤的區別
異常:指程序在編譯、運行期間發生了某種異常(XxxException),我們可以對異常進行具體的處理。若不處理異常,程序將會結束運行。
l 異常的產生演示如下:
public static void main(String[] args) {
int[] arr = new int[3];
System.out.println(arr[0]);
System.out.println(arr[3]);
// 該句運行時發生了數組索引越界異常ArrayIndexOutOfBoundsException,由於沒有處理異常,導致程序無法繼續執行,程序結束。
System.out.println("over"); // 由於上面代碼發生了異常,此句代碼不會執行
}
錯誤:指程序在運行期間發生了某種錯誤(XxxError),Error錯誤通常沒有具體的處理方式,程序將會結束運行。Error錯誤的發生往往都是系統級別的問題,都是jvm所在系統發生的,並反饋給jvm的。我們無法針對處理,只能修正代碼。
l 錯誤的產生演示如下:
public static void main(String[] args) {
int[] arr = new int[1024*1024*100];
//該句運行時發生了內存溢出錯誤OutOfMemoryError,開辟了過大的數組空間,導致JVM在分配數組空間時超出了JVM內存空間,直接發生錯誤。
}
拋出異常throw
在編寫程序時,我們必須要考慮程序出現問題的情況。比如,在定義方法時,方法需要接受參數。那么,當調用方法使用接受到的參數時,首先需要先對參數數據進行合法的判斷,數據若不合法,就應該告訴調用者,傳遞合法的數據進來。這時需要使用拋出異常的方式來告訴調用者。
在java中,提供了一個throw關鍵字,它用來拋出一個指定的異常對象。那么,拋出一個異常具體如何操作呢?
l 1,創建一個異常對象。封裝一些提示信息(信息可以自己編寫)。
l 2,需要將這個異常對象告知給調用者。怎么告知呢?怎么將這個異常對象傳遞到調用者處呢?通過關鍵字throw就可以完成。throw 異常對象;
throw用在方法內,用來拋出一個異常對象,將這個異常對象傳遞到調用者處,並結束當前方法的執行。
使用格式:
throw new 異常類名(參數);
例如:
throw new NullPointerException("要訪問的arr數組不存在");
throw new ArrayIndexOutOfBoundsException("該索引在數組中不存在,已超出范圍");
l 下面是異常類ArrayIndexOutOfBoundsException與NullPointerException的構造方法
學習完拋出異常的格式后,我們通過下面程序演示下throw的使用。
l 編寫工具類,提供獲取數組指定索引處的元素值
class ArrayTools{
//通過給定的數組,返回給定的索引對應的元素值。
public static int getElement(int[] arr,int index) {
/*
若程序出了異常,JVM它會打包異常對象並拋出。但是它所提供的信息不夠給力。想要更清晰,需要自己拋出異常信息。
下面判斷條件如果滿足,當執行完throw拋出異常對象后,方法已經無法繼續運算。這時就會結束當前方法的執行,並將異常告知給調用者。這時就需要通過異常來解決。
*/
if(arr==null){
throw new NullPointerException("arr指向的數組不存在");
}
if(index<0 || index>=arr.length){
throw new ArrayIndexOutOfBoundsException("錯誤的角標,"+index+"索引在數組中不存在");
}
int element = arr[index];
return element;
}
}
l 測試類
class ExceptionDemo3 {
public static void main(String[] args) {
int[] arr = {34,12,67}; //創建數組
int num = ArrayTools.getElement(null,2);// 調用方法,獲取數組中指定索引處元素
//int num = ArrayTools.getElement(arr,5);// 調用方法,獲取數組中指定索引處元素
System.out.println("num="+num);//打印獲取到的元素值
}
}
聲明異常throws
聲明:將問題標識出來,報告給調用者。如果方法內通過throw拋出了編譯時異常,而沒有捕獲處理(稍后講解該方式),那么必須通過throws進行聲明,讓調用者去處理。
聲明異常格式:
修飾符 返回值類型 方法名(參數) throws 異常類名1,異常類名2… { }
聲明異常的代碼演示:
class Demo{
/*
如果定義功能時有問題發生需要報告給調用者。可以通過在方法上使用throws關鍵字進行聲明。
*/
public void show(int x)throws Exception {
if(x>0){
throw new Exception();
} else {
System.out.println("show run");
}
}
}
throws用於進行異常類的聲明,若該方法可能有多種異常情況產生,那么在throws后面可以寫多個異常類,用逗號隔開。
多個異常的情況,例如:
public static int getElement(int[] arr,int index) throws NullPointerException, ArrayIndexOutOfBoundsException {
if(arr==null){
throw new NullPointerException("arr指向的數組不存在");
}
if(index<0 || index>=arr.length){
throw new ArrayIndexOutOfBoundsException("錯誤的角標,"+index+"索引在數組中不存在");
}
int element = arr[index];
return element;
}
捕獲異常try…catch…finally
捕獲:Java中對異常有針對性的語句進行捕獲,可以對出現的異常進行指定方式的處理
捕獲異常格式:
try {
//需要被檢測的語句。
}
catch(異常類 變量) { //參數。
//異常的處理語句。
}
finally {
//一定會被執行的語句。
}
try:該代碼塊中編寫可能產生異常的代碼。
catch:用來進行某種異常的捕獲,實現對捕獲到的異常進行處理。
finally:有一些特定的代碼無論異常是否發生,都需要執行。另外,因為異常會引發程序跳轉,導致有些語句執行不到。而finally就是解決這個問題的,在finally代碼塊中存放的代碼都是一定會被執行的。
演示如下:
class ExceptionDemo{
public static void main(String[] args){ //throws ArrayIndexOutOfBoundsException
try {
int[] arr = new int[3];
System.out.println( arr[5] );// 會拋出ArrayIndexOutOfBoundsException
當產生異常時,必須有處理方式。要么捕獲,要么聲明。
}
catch (ArrayIndexOutOfBoundsException e) { //括號中需要定義什么呢?try中拋出的是什么異常,在括號中就定義什么異常類型。
System.out.println("異常發生了");
} finally {
arr = null; //把數組指向null,通過垃圾回收器,進行內存垃圾的清除
}
System.out.println("程序運行結果");
}
}
try…catch…finally異常處理的組合方式
l try catch finally組合:檢測異常,並傳遞給catch處理,並在finally中進行資源釋放。
l try catch組合 : 對代碼進行異常檢測,並對檢測的異常傳遞給catch處理。對異常進行捕獲處理。
void show(){ //不用throws
try{
throw new Exception();//產生異常,直接捕獲處理
}catch(Exception e){
//處理方式
}
}
l 一個try 多個catch組合 : 對代碼進行異常檢測,並對檢測的異常傳遞給catch處理。對每種異常信息進行不同的捕獲處理。
void show(){ //不用throws
try{
throw new Exception();//產生異常,直接捕獲處理
}catch(XxxException e){
//處理方式
}catch(YyyException e){
//處理方式
}catch(ZzzException e){
//處理方式
}
}
注意:這種異常處理方式,要求多個catch中的異常不能相同,並且若catch中的多個異常之間有子父類異常的關系,那么子類異常要求在上面的catch處理,父類異常在下面的catch處理。
l try finally 組合: 對代碼進行異常檢測,檢測到異常后因為沒有catch,所以一樣會被默認jvm拋出。異常是沒有捕獲處理的。但是功能所開啟資源需要進行關閉,所有finally。只為關閉資源。
void show(){//需要throws
try{
throw new Exception();
}finally {
//釋放資源
}
}
運行時期異常
l RuntimeException和他的所有子類異常,都屬於運行時期異常。NullPointerException,ArrayIndexOutOfBoundsException等都屬於運行時期異常.
l 運行時期異常的特點:
n 方法中拋出運行時期異常,方法定義中無需throws聲明,調用者也無需處理此異常
n 運行時期異常一旦發生,需要程序人員修改源代碼.
class ExceptionDemo{
public static void main(String[] args){
method();
}
public static void method(){
throw new RuntimeException();
}
}
異常在方法重寫中細節
l 子類覆蓋父類方法時,如果父類的方法聲明異常,子類只能聲明父類異常或者該異常的子類,或者不聲明。
例如:
class Fu {
public void method () throws RuntimeException {
}
}
class Zi extends Fu {
public void method() throws RuntimeException { } //拋出父類一樣的異常
//public void method() throws NullPointerException{ } //拋出父類子異常
}
l 當父類方法聲明多個異常時,子類覆蓋時只能聲明多個異常的子集。
例如:
class Fu {
public void method () throws NullPointerException, ClassCastException{
}
}
class Zi extends Fu {
public void method()throws NullPointerException, ClassCastException { } public void method() throws NullPointerException{ } //拋出父類異常中的一部分
public void method() throws ClassCastException { } //拋出父類異常中的一部分
}
3、當被覆蓋的方法沒有異常聲明時,子類覆蓋時無法聲明異常的。
例如:
class Fu {
public void method (){
}
}
class Zi extends Fu {
public void method() throws Exception { }//錯誤的方式
}
舉例:父類中會存在下列這種情況,接口也有這種情況
問題:接口中沒有聲明異常,而實現的子類覆蓋方法時發生了異常,怎么辦?
答:無法進行throws聲明,只能catch的捕獲。萬一問題處理不了呢?catch中繼續throw拋出,但是只能將異常轉換成RuntimeException子類拋出。
interface Inter {
public abstract void method();
}
class Zi implements Inter {
public void method(){ //無法聲明 throws Exception
int[] arr = null;
if (arr == null) {
//只能捕獲處理
try{
throw new Exception(“哥們,你定義的數組arr是空的!”);
} catch(Exception e){
System.out.println(“父方法中沒有異常拋出,子類中不能拋出Exception異常”);
//我們把異常對象e,采用RuntimeException異常方式拋出
throw new RuntimeException(e);
}
}
}
}
異常中常用方法
在Throwable類中為我們提供了很多操作異常對象的方法,常用的如下:
l getMessage方法:返回該異常的詳細信息字符串,即異常提示信息
l toString方法:返回該異常的名稱與詳細信息字符串
l printStackTrace:在控制台輸出該異常的名稱與詳細信息字符串、異常出現的代碼位置
異常的常用方法代碼演示:
try {
Person p= null;
if (p==null) {
throw new NullPointerException(“出現空指針異常了,請檢查對象是否為null”);
}
} catch (NullPointerException e) {
String message = e.getMesage();
System.out.println(message );
String result = e.toString();
System.out.println(result);
e.printStackTrace();
}
自定義異常
在上述代碼中,發現這些異常都是JDK內部定義好的,並且這些異常不好找。書寫時也很不方便,那么能不能自己定義異常呢?
之前的幾個異常都是java通過類進行的描述。並將問題封裝成對象,異常就是將問題封裝成了對象。這些異常不好認,書寫也很不方便,能不能定義一個符合我的程序要求的異常名稱。既然JDK中是使用類在描述異常信息,那么我們也可以模擬Java的這種機制,我們自己定義異常的信息,異常的名字,讓異常更符合自己程序的閱讀。准確對自己所需要的異常進行類的描述。
自定義異常類的定義
首先要搞清楚RunTimeException還是非運行時期異常
通過閱讀異常源代碼:發現java中所有的異常類,都是繼承Throwable,或者繼承Throwable的子類。這樣該異常才可以被throw拋出。
說明這個異常體系具備一個特有的特性:可拋性:即可以被throw關鍵字操作。
並且查閱異常子類源碼,發現每個異常中都調用了父類的構造方法,把異常描述信息傳遞給了父類,讓父類幫我們進行異常信息的封裝。
例如NullPointerException異常類源代碼:
public class NullPointerException extends RuntimeException {
public NullPointerException() {
super();//調用父類構造方法
}
public NullPointerException(String s) {
super(s);//調用父類具有異常信息的構造方法
}
}
現在,我們來定義個自己的異常,即自定義異常。
格式:
Class 異常名 extends Exception{ //或繼承RuntimeException
public 異常名(){
}
public 異常名(String s){
super(s);
}
}
l 自定義異常繼承Exception演示
class MyException extends Exception{
/*
為什么要定義構造函數,因為看到Java中的異常描述類中有提供對異常對象的初始化方法。
*/
public MyException(){
super();
}
public MyException(String message) {
super(message);// 如果自定義異常需要異常信息,可以通過調用父類的帶有字符串參數的構造函數即可。
}
}
l 自定義異常繼承RuntimeException演示
class MyException extends RuntimeException{
/*
為什么要定義構造函數,因為看到Java中的異常描述類中有提供對異常對象的初始化方法。
*/
MyException(){
super();
}
MyException(String message) {
super(message);// 如果自定義異常需要異常信息,可以通過調用父類的帶有字符串參數的構造函數即可。
}
}
自定義異常的練習
定義Person類,包含name與age兩個成員變量。
在Person類的有參數構造方法中,進行年齡范圍的判斷,若年齡為負數或大於200歲,則拋出NoAgeException異常,異常提示信息“年齡數值非法”。
要求:在測試類中,調用有參數構造方法,完成Person對象創建,並進行異常的處理。
l 自定義異常類
class NoAgeException extends Exception{
NoAgeException() {
super();
}
NoAgeException(String message) {
super(message);
}
}
l Person類
class Person{
private String name;
private int age;
Person(String name,int age) throws NoAgeException {
//加入邏輯判斷。
if(age<0 || age>200) {
throw new NoAgeException(age+",年齡數值非法");
}
this.name = name;
this.age = age;
}
//定義Person對象對應的字符串表現形式。覆蓋Object中的toString方法。
public String toString() {
return "Person[name="+name+",age="+age+"]";
}
}
l 測試類
class ExceptionDemo{
public static void main(String[] args) {
try {
Person p = new Person("xiaoming",20);
System.out.println(p);
}
catch (NoAgeException ex){
System.out.println("年齡異常啦");
}
System.out.println("over");
}
}
總結一下,構造函數到底拋出這個NoAgeException是繼承Exception呢?還是繼承RuntimeException呢?
l 繼承Exception,必須要throws聲明,一聲明就告知調用者進行捕獲,一旦問題處理了調用者的程序會繼續執行。
l 繼承RuntimeExcpetion,不需要throws聲明的,這時調用是不需要編寫捕獲代碼的,因為調用根本就不知道有問題。一旦發生NoAgeException,調用者程序會停掉,並有jvm將信息顯示到屏幕,讓調用者看到問題,修正代碼。