淺談JAVA-異常處理機制

淺談Java異常處理機制

(-----文中重點信息將用紅色字體凸顯-----)


一、Java異常簡述:       

        程序異常從某種意義上說就是程序發生了錯誤,使程序無法再繼續執行下去;但它和錯誤還是有區別的。例如,我們將關鍵字public寫成了pbulic,那麼虛擬機是無法編譯通過的,這就屬於嚴格意義上的錯誤,需要程序員更正過後程序才能編譯成功。

        程序員在寫代碼的過程中應該避免書寫上的錯誤,但在實際的軟件開發中,一個項目是由多個人員共同完成,他們會互相引用對方完成的程序,有的“錯誤”是由於調用者不當操作而引發的,這是原程序員無法預料的,這種情況就需要原程序員對可能發生的問題進行預先處理。例如如下代碼:

<span style="font-size:14px;">class Demo1
{
	public static void main(String[] args) 
	{
		Demo d = new Demo(5,0);
		d.getAnswer();
	}
}
class Demo
{
	int a,b;
	Demo(int a,int b)
	{
		this.a=a;
		this.b=b;
	}
	void getAnswer()
	{
		System.out.println("a/b="+(a/b));
	}
}</span>

       

        以上代碼,假設Demo類是由原程序員編寫的代碼,而調用者在類Demo1的主函數中建立了類Demo的對象並調用了類Demo中的getAnswer()方法,則當調用者將b定義爲0時,將發生java.lang.Arithmetic.Exception:/ by zero異常。

        從常識上來說,我們也該知道數學計算中除數不能爲0,調用者在不知情的情況下將b設定爲0就引發了錯誤。其實原程序員在書寫此段代碼的時候就應該考慮到調用者可能將除數設置爲0的情況,他就應該對這種情況進行處理。

        但是該怎麼處理呢?這就引出了本文的主題-----Java異常機制。


二、Java異常機制

        Java把異常當作對象來處理,並定義了Throwable類作爲所有異常和錯誤類的超類(父類),Throwable類下有兩個小弟,分別是Error和Exception,其主要構架如下:

<span style="font-size:14px;">Throwable

        |--Error

        |--Exception</span>

        如果程序發生Error,是無法通過編譯的,需要程序員手動對代碼進行修整後在編譯;一般不編寫針對性的代碼對其進行處理。

        而對於Exception異常類,它又可以分爲編譯時異常(CheckedException)和運行時異常(UncheckedException,又叫做RuntimeException),RuntimeException有其特殊之處,我們將在下面進行詳細介紹。對於Exception異常,我們可以預先書寫相關代碼對可能發生問題的代碼進行處理,而如何處理就是本文要講述的重點。

      

三、Exception異常詳述:

        1.異常處理格式:

<span style="font-size:14px;">        try
	{
		可能觸發異常的代碼;
	}
	catch(異常類 變量)
	{
		具體處理方式;
	}
	finally
	{
		一定會運行的代碼,一般用於釋放內存資源;
	}</span>
        我們將可能觸發異常的代碼放在try{}語句塊中,當它發生異常後會產生一個異常對象並由catch{}塊捕獲並處理,處理的方式有兩種,一種是拋出異常(用throw語句),一種是直接對發生的異常進行處理,比如可以輸出異常信息(在實際項目編程中我們不建議通過打印異常信息作爲處理方式,因爲對於專業的程序員來說這些異常信息他都比較熟悉了,沒有必要而且也無法解決實際存在的問題,我們一般要針對異常做出合理的動作進行處理)。

       將本文第一個程序套用異常處理格式後:

<span style="font-size:14px;">class Demo1
{
	public static void main(String[] args) 
	{
		try
		{
			Demo d = new Demo(5,0);
			d.getAnswer();
			
		}
		catch (ArithmeticException e)
		{
			System.out.println("除零啦!");
		}
	}
}
class Demo
{
	int a,b;
	Demo(int a,int b)
	{
		this.a=a;
		this.b=b;
	}
	void getAnswer()
	{
		System.out.println("a/b="+(a/b));
	}
}</span>

        如果異常沒有發生,則不會產生異常對象,那麼catch(){}語句塊則不會接收異常對象,從而裏面的代碼不會執行。

        對於打印異常對象信息,有如下方法可以實現:

        <span style="font-size:14px;">System.out.println(e.getMessage());
	System.out.println(e.toString());
	e.printStackTrace();</span>

        以上三種方法,打印異常對象信息豐富程度依次遞增。

        2.throws和throw:

        做爲原程序員,如若在編寫代碼時意識到調用者可能觸發異常,那麼可以對調用者調用時可能發生異常的代碼進行"異常聲明",也就是通過"throws"拋出異常對象來提醒調用者必須對次此拋出動作進行處理或者繼續拋出異常(懶鬼^-^),如果調用者不進行處理(就是套try...catch格式)或繼續拋出動作,那麼程序無法編譯通過。

        繼續拋出格式:

<span style="font-size:14px;">class Demo1
{
	public static void main(String[] args) throws ArithmeticException
	{
			Demo d = new Demo(5,0);
			d.getAnswer();	
	}
}
class Demo
{
	int a,b;
	Demo(int a,int b)
	{
		this.a=a;
		this.b=b;
	}
	void getAnswer() throws ArithmeticException
	{
		System.out.println("a/b="+(a/b));
	}
}</span>

        從以上代碼中可以看出,由於Demo類中拋出了異常,主函數調用可能存在問題的方法時也拋出了相同的異常,這樣這個程序便可以通過編譯,但是遺憾的是還是發生了錯誤,因爲除數爲0啦!

        也許很多同仁會有個疑問,如果調用者也是拋出異常而不進行處理,那麼異常這個"燙手的山芋"拋到什麼時候是個頭啊?其實如果異常不停的聲明拋出則最終異常會拋給虛擬機(VM),虛擬機最終輸出異常信息作爲結束,程序也不能理想的執行。

        需要在這裏提一句:在拋出異常時,一定要有針對性的拋出,空指針異常、數組指標越界異常、算術異常...是什麼異常就拋出什麼異常,不建議總是盲目的拋出"throws Exception",這個過於籠統。

        有的時候需要拋出異常的代碼中可能包含了多種可能發生的異常,這個時候我們就根據具體情況拋出多種異常,每種異常之間用","隔開;調用者調用可能發生異常的代碼時也應該對應的拋出多個異常。如果調用者選擇處理,則catch塊則應該同拋出異常的種類和個數匹配(當然你也可以只定義一個catch塊,catch塊中定義"Exception e",如此則此catch塊可以接受任何形式的異常對象,但是這樣沒有針對性從而降低程序的健壯性);可以瞭解到,不可能有兩個或兩個以上的catch塊一起執行,函數中有異常發生時,此行代碼之後的代碼都不會被執行了。

        在拋出多個異常時,對應使用catch塊進行處理,如果這些catch中的異常類存在子父關係,則父類異常應該放在下面;如果包含父類異常對象的catch塊放在包含子類異常對象的catch塊之上,則後者無法獲取執行機會。

	try
	{
		Demo d = new Demo(5,0);
		d.getAnswer();	

	}
	catch (ArithmeticException e)
	{
		System.out.println(e.toString());
	}
	catch (ArrayIndexOutOfBoundsException e)
	{
		System.out.println(e.toString());
	}
	catch (Exception e)
	{
		System.out.println(e.toString());
	}


        在講述throw之前插播一小節-----自定義異常,否則單獨講述throw不易理解。

        3.自定義異常:

        雖然Java已經爲我們定義了種類豐富品種齊全的異常類,但是還是會有一些異常無法同現成異常完全匹配,這時我們就可以自己定義異常類,來描述和解決特殊問題。

        我們都知道Java中的老大是Object,而異常的老大就是Throwable,在Throwable類中已經定義了異常信息及獲取異常信息的方法:

	class Throwable
	{
		private String message;
		Throwable(String message)
		{
			this.message=message;
		}
		public String getMessage()
		{
			return message;
		}
	}
        而Throwable類的子類Exception類也定義了類似信息和獲取信息的方法,不過二者具有繼承關係,則可以如此定義Exception類:

	class Exception extends Throwable
	{
		Exception(String message)
		{
			super(message);
		}
	}

        我們在定義自定義異常時,需要繼承我們在定義自定義異常時需要繼承Exception類,由於我們自己建立的自定義異常類虛擬機是無法識別的,所以我們需要拋出(使用throw拋出異常類對象,例如:throw new MyException();)自定義異常類對象讓虛擬機獲取,這時包含拋出自定義異常類對象的方法也需要拋出(throws MyExcepiton)異常信息,二者都指向自定義異常類。

        下面本人自定義一個異常並定義獲取異常信息的方法:

class ZiDYException extends Exception
{
	private int value;
	ZiDYException(String msg,int value)
	{
		super(msg);
		this.value = value;
	}
	public int getValue()
	{
		return value;
	}
}
class GJClass
{
	int div(int a,int b) throws ZiDYException
	{
		if (b<=0)
		{
			throw new ZiDYException("被除數不能≤0!",b);//拋出一個自定義異常對象;
		}
		return a/b;
	}
}

class ZiDYExceptionTest 
{
	public static void main(String[] args) 
	{
		GJClass g = new GJClass();
		try
		{
			int x = g.div(4,-1);
			System.out.println("x="+x);
		}
		catch (ZiDYException e)
		{
			System.out.println(e.toString());
			System.out.println("被除數不符合要求,請重新輸入!");
			System.out.println("壞因子是:"+e.getValue());
		}
		System.out.println("over!");
	}
}
        以上代碼中我在定義除法的時候拋出了異常對象,同時此方法也拋出了自定義異常類;主函數在調用拋出了異常的方法div時需要進行處理,要麼繼續拋出要麼就使用try...catch塊來進行處理,可以看到catch塊中定義的異常對象爲自定義異常類對象,否則無法調用我異常類中的getValue()方法。
        詳細講解下拋出自定義異常類對象:

	int div(int a,int b) throws ZiDYException
	{
		if (b<=0)
		{
			throw new ZiDYException("被除數不能≤0!",b);//拋出一個自定義異常對象;
		}
		return a/b;
	}
        上面代碼中,我在拋出自定義異常類對象時加入了異常信息和異常因子,這些都是要在定義自定義異常類時的構造函數相對應的。當程序發生異常時,就會輸出自定義的異常信息。

        這裏插入一句:只有屬於Throwable類及其子類才具有可拋性。

        現在再來補充講解一下throws和throw的區別:throws使用在函數上,throw使用在函數內;throws後面跟異常類,可以跟多個,每個異常類之間用","隔開,throw後跟的是異常對象。

        4.屌炸天的RuntimeException:

        前面也提到了這個RuntimeException類,對於此類及其子類,如果在函數內容拋出了該異常類或子類對象,則函數上可以不用聲明,編譯照樣可以通過;如果在函數上聲明瞭該異常,調用者可以不用進行處理,編譯依然可以通過。這樣做的原因在於出現此類問題時,虛擬機希望程序停下來讓程序員修正程序,而不希望這個問題被處理所"隱藏"。

        一般來說,這類問題不是定義方法的程序員的錯,而是調用者沒有輸入合理的數值導致異常的發生,所以有必要也有可能對出現異常的代碼就行更正。我們在定義自定義異常時,如果異常發生後導致程序無法繼續執行,則可以將自定義異常類繼承RuntimeException類。

        5.finally和try...catch語句的嵌套:

        finally語句也是異常處理代碼塊的一部分,在此代碼塊中書寫的程序無論異常是否處理都會運行,一般我們利用finally塊來對內存進行釋放。

        在有的特殊情況,try...catch語句可以進行嵌套,由此對異常進行多次判斷,這個有點像for語句的嵌套,不過不是一回事。

//當捕獲到的異常本功能處理不了時可以繼續在catch塊中拋出
	public void method()
	{
		try
		{
			throw new Exception();
		}
		catch(Exception e)
		{
			try
			{
				throw e;
			}
			catch
			{
				System.out.println(“異常嵌套");
			}
		}
	}


四、零散知識點總結:

        1.一旦在finally塊中使用了return或throw語句,將會導致try塊、catch塊中的return、throw語句失效。

        2.如果兩個類具有父子關係,則:① 若子類覆蓋父類方法,父類方法拋出異常,則子類只能拋出與父類相同的異常或父類異常的子異常; ② 若子類覆蓋父類方法,子類方法包含多個異常,則子類只能拋出與父類相同的異常或父類異常的子異常,剩餘的其他異常則必須通過try..catch來處理;③ 若子類覆蓋父類方法,如果父類方法沒有拋出異常,則子類方法也不能拋出異常,如果子類方法確實有異常存在則必須通過try...catch來處理。

        3.try/catch/finally三個塊可以三個塊同時出現,也可以try塊和catch塊或者finally搭配,不允許單個塊獨自出現。

        4.finally塊有一種情況不會執行,就是執行到"System.exit(0);"語句時。



        本文就寫到這兒啦!有的知識點沒有詳述,如果朋友們有興趣可以給我留言,我會及時回覆的;文中難免有問題,還請各位讀者海涵啦^..^!






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章