迷失北京

          BlogJava 聯(lián)系 聚合 管理
            60 Posts :: 0 Stories :: 13 Comments :: 0 Trackbacks

          一. 異常的定義

          在《java編程思想》中這樣定義 異常:阻止當(dāng)前方法或作用域繼續(xù)執(zhí)行的問題。雖然java中有異常處理機制,但是要明確一點,決不應(yīng)該用"正常"的態(tài)度來看待異常。絕對一點說異常就是某種意義上的錯誤,就是問題,它可能會導(dǎo)致程序失敗。之所以java要提出異常處理機制,就是要告訴開發(fā)人員,你的程序出現(xiàn)了不正常的情況,請注意。

          記得當(dāng)初學(xué)習(xí)java的時候,異常總是搞不太清楚,不知道這個異常是什么意思,為什么會有這個機制?但是隨著知識的積累逐漸也對異常有一點感覺了。舉一個例子來說明一下異常的用途。

           

          public class Calculator {
          	public int devide(int num1, int num2) {
          		//判斷除數(shù)是否為0
          		if(num2 == 0) {
          			throw new IllegalArgumentException("除數(shù)不能為零");
          		}
          		
          		return num1/num2;
          	}
          }
          

           

          看一下這個類中關(guān)于除運算的方法,如果你是新手你可能會直接返回計算結(jié)果,根本不去考慮什么參數(shù)是否正確,是否合法(當(dāng)然可以原諒,誰都是這樣過來的)。但是我們應(yīng)盡可能的考慮周全,把可能導(dǎo)致程序失敗的"苗頭"扼殺在搖籃中,所以進行參數(shù)的合法性檢查就很有必要了。其中執(zhí)行參數(shù)檢查拋出來的那個參數(shù)非法異常,這就屬于這個方法的不正常情況。正常情況下我們會正確的使用計算器,但是不排除粗心大意把除數(shù)賦值為0。如果你之前沒有考慮到這種情況,并且恰巧用戶數(shù)學(xué)基礎(chǔ)不好,那么你完了。但是如果你之前考慮到了這種情況,那么很顯然錯誤已在你的掌控之中。

           

          二. 異常掃盲行動

           

          今天和別人聊天時看到一個笑話:世界上最真情的相依,是你在try我在catch。無論你發(fā)神馬脾氣,我都默默承受,靜靜處理。 大多數(shù)新手對java異常的感覺就是:try...catch...。沒錯,這是用的最多的,也是最實用的。我的感覺就是:java異常是從"try...catch..."走來。

           

          首先來熟悉一下java的異常體系:

           

          Throwable 類是 Java 語言中所有錯誤或異常的超類(這就是一切皆可拋的東西)。它有兩個子類:ErrorException

           

          Error用于指示合理的應(yīng)用程序不應(yīng)該試圖捕獲的嚴重問題。這種情況是很大的問題,大到你不能處理了,所以聽之任之就行了,你不用管它。比如說VirtualMachineError:當(dāng) Java 虛擬機崩潰或用盡了它繼續(xù)操作所需的資源時,拋出該錯誤。好吧,就算這個異常的存在了,那么應(yīng)該何時,如何處理它呢??交給JVM吧,沒有比它更專業(yè)的了。

           

          Exception它指出了合理的應(yīng)用程序想要捕獲的條件。Exception又分為兩類:一種是CheckedException,一種是UncheckedException。這兩種Exception的區(qū)別主要是CheckedException需要用try...catch...顯示的捕獲,而UncheckedException不需要捕獲。通常UncheckedException又叫做RuntimeException。《effective java》指出:對于可恢復(fù)的條件使用被檢查的異常(CheckedException),對于程序錯誤(言外之意不可恢復(fù),大錯已經(jīng)釀成)使用運行時異常(RuntimeException)。

           

          我們常見的RuntimeExcepitonIllegalArgumentException、IllegalStateExceptionNullPointerExceptionIndexOutOfBoundsException等等。對于那些CheckedException就不勝枚舉了,我們在編寫程序過程中try...catch...捕捉的異常都是CheckedExceptionio包中的IOException及其子類,這些都是CheckedException

           

           

           

          三. 異常的使用

           

          在異常的使用這一部分主要是演示代碼,都是我們平常寫代碼的過程中會遇到的(當(dāng)然只是一小部分),拋磚引玉嗎!

           

          1. 這個例子主要通過兩個方法對比來演示一下有了異常以后代碼的執(zhí)行流程。 

           

          public static void testException1() {
          		int[] ints = new int[] { 1, 2, 3, 4 };
          		System.out.println("異常出現(xiàn)前");
          		try {
          			System.out.println(ints[4]);
          			System.out.println("我還有幸執(zhí)行到嗎");// 發(fā)生異常以后,后面的代碼不能被執(zhí)行
          		} catch (IndexOutOfBoundsException e) {
          			System.out.println("數(shù)組越界錯誤");
          		}
          		System.out.println("異常出現(xiàn)后");
          	}
          	/*output:
          	異常出現(xiàn)前
          	數(shù)組越界錯誤
          	4
          	異常出現(xiàn)后
          	*/
          

           

            

          public static void testException2() {
          		int[] ints = new int[] { 1, 2, 3, 4 };
          		System.out.println("異常出現(xiàn)前");
          		System.out.println(ints[4]);
          		System.out.println("我還有幸執(zhí)行到嗎");// 發(fā)生異常以后,他后面的代碼不能被執(zhí)行
          	}
          

            

          首先指出例子中的不足之處,IndexOutofBoundsException是一個非受檢異常,所以不用try...catch...顯示捕捉,但是我的目的是對同一個異常用不同的處理方式,看它會有什么不同的而結(jié)果(這里也就只能用它將就一下了)。異常出現(xiàn)時第一個方法只是跳出了try塊,但是它后面的代碼會照樣執(zhí)行的。但是第二種就不一樣了直接跳出了方法,比較強硬。從第一個方法中我們看到,try...catch...是一種"事務(wù)性"的保障,它的目的是保證程序在異常的情況下運行完畢,同時它還會告知程序員程序中出錯的詳細信息(這種詳細信息有時要依賴于程序員設(shè)計)。

          2. 重新拋出異常

           

          public class Rethrow {
          	public static void readFile(String file) throws FileNotFoundException {
          		try {
          			BufferedInputStream in = new BufferedInputStream(new FileInputStream(file));
          		} catch (FileNotFoundException e) {
          			e.printStackTrace();
          			System.err.println("不知道如何處理該異常或者根本不想處理它,但是不做處理又不合適,這是重新拋出異常交給上一級處理");
          			//重新拋出異常
          			throw e;
          		}
          	}
          	
          	public static void printFile(String file) {
          		try {
          			readFile(file);
          		} catch (FileNotFoundException e) {
          			e.printStackTrace();
          		}
          	}
          	
          	public static void main(String[] args) {
          		printFile("D:/file");
          	}
          }
          

           

            

          異常的本意是好的,讓我們試圖修復(fù)程序,但是現(xiàn)實中我們修復(fù)的幾率很小,我們很多時候就是用它來記錄出錯的信息。如果你厭倦了不停的處理異常,重新拋出異常對你來說可能是一個很好的解脫。原封不動的把這個異常拋給上一級,拋給調(diào)用這個方法的人,讓他來費腦筋吧。這樣看來,java異常(當(dāng)然指的是受檢異常)又給我們平添很多麻煩,盡管它的出發(fā)點是好的。

           

          3. 異常鏈的使用及異常丟失

          定義三個異常類:ExceptionA,ExceptionB,ExceptionC

           

          public class ExceptionA extends Exception {
          	public ExceptionA(String str) {
          		super();
          	}
          }
          
          public class ExceptionB extends ExceptionA {
          
          	public ExceptionB(String str) {
          		super(str);
          	}
          }
          
          public class ExceptionC extends ExceptionA {
          	public ExceptionC(String str) {
          		super(str);
          	}
          }

          異常丟失的情況:

           

          public class NeverCaught {
          	static void f() throws ExceptionB{
          		throw new ExceptionB("exception b");
          	}
          
          	static void g() throws ExceptionC {
          		try {
          			f();
          		} catch (ExceptionB e) {
          			ExceptionC c = new ExceptionC("exception a");
          			throw c;
          		}
          	}
          
          	public static void main(String[] args) {
          			try {
          				g();
          			} catch (ExceptionC e) {
          				e.printStackTrace();
          			}
          	}
          
          }
          /*
          exception.ExceptionC
          at exception.NeverCaught.g(NeverCaught.java:12)
          at exception.NeverCaught.main(NeverCaught.java:19)
          */

          為什么只是打印出來了ExceptionC而沒有打印出ExceptionB呢?這個還是自己分析一下吧!

          上面的情況相當(dāng)于少了一種異常,這在我們排錯的過程中非常的不利。那我們遇到上面的情況應(yīng)該怎么辦呢?這就是異常鏈的用武之地:保存異常信息,在拋出另外一個異常的同時不丟失原來的異常。

           

          public class NeverCaught {
          	static void f() throws ExceptionB{
          		throw new ExceptionB("exception b");
          	}
          
          	static void g() throws ExceptionC {
          		try {
          			f();
          		} catch (ExceptionB e) {
          			ExceptionC c = new ExceptionC("exception a");
          			//異常連
          			c.initCause(e);
          			throw c;
          		}
          	}
          
          	public static void main(String[] args) {
          			try {
          				g();
          			} catch (ExceptionC e) {
          				e.printStackTrace();
          			}
          	}
          
          }
          /*
          exception.ExceptionC
          at exception.NeverCaught.g(NeverCaught.java:12)
          at exception.NeverCaught.main(NeverCaught.java:21)
          Caused by: exception.ExceptionB
          at exception.NeverCaught.f(NeverCaught.java:5)
          at exception.NeverCaught.g(NeverCaught.java:10)
          ... 1 more
          */

          這個異常鏈的特性是所有異常均具備的,因為這個initCause()方法是從Throwable繼承的。

          4. 清理工作

          清理工作對于我們來說是必不可少的,因為如果一些消耗資源的操作,比如IO,JDBC。如果我們用完以后沒有及時正確的關(guān)閉,那后果會很嚴重,這意味著內(nèi)存泄露。異常的出現(xiàn)要求我們必須設(shè)計一種機制不論什么情況下,資源都能及時正確的清理。這就是finally

           

          public void readFile(String file) {
          		BufferedReader reader = null;
          		try {
          			reader = new BufferedReader(new InputStreamReader(
          					new FileInputStream(file)));
          			// do some other work
          		} catch (FileNotFoundException e) {
          			e.printStackTrace();
          		} finally {
          			try {
          				reader.close();
          			} catch (IOException e) {
          				e.printStackTrace();
          			}
          		}
          	}

          例子非常的簡單,是一個讀取文件的例子。這樣的例子在JDBC操作中也非常的常見。(所以,我覺得對于資源的及時正確清理是一個程序員的基本素質(zhì)之一。)

          Try...finally結(jié)構(gòu)也是保證資源正確關(guān)閉的一個手段。如果你不清楚代碼執(zhí)行過程中會發(fā)生什么異常情況會導(dǎo)致資源不能得到清理,那么你就用try對這段"可疑"代碼進行包裝,然后在finally中進行資源的清理。舉一個例子:

           

          public void readFile() {
          		BufferedReader reader = null;
          		try {
          			reader = new BufferedReader(new InputStreamReader(
          					new FileInputStream("file")));
          			// do some other work
          		
          			//close reader
          			reader.close();
          		} catch (FileNotFoundException e) {
          			e.printStackTrace();
          		} catch (IOException e) {
          			e.printStackTrace();
          		} 
          	}
          

           

          我們注意一下這個方法和上一個方法的區(qū)別,下一個人可能習(xí)慣更好一點,及早的關(guān)閉reader。但是往往事與愿違,因為在reader.close()以前異常隨時可能發(fā)生,這樣的代碼結(jié)構(gòu)不能預(yù)防任何異常的出現(xiàn)。因為程序會在異常出現(xiàn)的地方跳出,后面的代碼不能執(zhí)行(這在上面應(yīng)經(jīng)用實例證明過)。這時我們就可以用try...finally來改造:

           

          public void readFile() {
          		BufferedReader reader = null;
          		try {
          			try {
          				reader = new BufferedReader(new InputStreamReader(
          						new FileInputStream("file")));
          				// do some other work
          
          				// close reader
          			} finally {
          				reader.close();
          			}
          		} catch (FileNotFoundException e) {
          			e.printStackTrace();
          		} catch (IOException e) {
          			e.printStackTrace();
          		}
          	}

          及早的關(guān)閉資源是一種良好的行為,因為時間越長你忘記關(guān)閉的可能性越大。這樣在配合上try...finally就保證萬無一失了(不要嫌麻煩,java就是這么中規(guī)中矩)

          再說一種情況,假如我想在構(gòu)造方法中打開一個文件或者創(chuàng)建一個JDBC連接,因為我們要在其他的方法中使用這個資源,所以不能在構(gòu)造方法中及早的將這個資源關(guān)閉。那我們是不是就沒轍了呢?答案是否定的。看一下下面的例子:

           

          public class ResourceInConstructor {
          	BufferedReader reader = null;
          	public ResourceInConstructor() {
          		try {
          			reader = new BufferedReader(new InputStreamReader(new FileInputStream("")));
          		} catch (FileNotFoundException e) {
          			e.printStackTrace();
          		}
          	}
          	
          	public void readFile() {
          		try {
          			while(reader.readLine()!=null) {
          				//do some work
          			}
          		} catch (IOException e) {
          			e.printStackTrace();
          		}
          	}
          	
          	public void dispose() {
          		try {
          			reader.close();
          		} catch (IOException e) {
          			e.printStackTrace();
          		}
          	}
          }

          這一部分講的多了一點,但是異常確實是看起來容易用起來難的東西呀,java中還是有好多的東西需要深挖的。

           

          四. 異常的誤用

          對于異常的誤用著實很常見,上一部分中已經(jīng)列舉了幾個,大家仔細的看一下。下面再說兩個其他的。

          1. 用一個Exception來捕捉所有的異常,頗有"一夫當(dāng)關(guān)萬夫莫開"的氣魄。不過這也是最傻的行為。

           

          public void readFile(String file) {
          		BufferedReader reader = null;
          		Connection conn = null;
          		try {
          			reader = new BufferedReader(new InputStreamReader(
          					new FileInputStream(file)));
          			// do some other work
          			
          			conn = DriverManager.getConnection("");
          			//...
          		} catch (Exception e) {
          			e.printStackTrace();
          		} finally {
          			try {
          				reader.close();
          				conn.close();
          			} catch (Exception e) {
          				e.printStackTrace();
          			}
          		}
          	}
          

           

            

          從異常角度來說這樣嚴格的程序確實是萬無一失,所有的異常都能捕獲。但是站在編程人員的角度,萬一這個程序出錯了我們該如何分辨是到底是那引起的呢,IO還是JDBC...所以,這種寫法很值得當(dāng)做一個反例。大家不要以為這種做法很幼稚,傻子才會做。我在公司實習(xí)時確實看見了類似的情況:只不過是人家沒有用Exception而是用了Throwable

          2. 這里就不舉例子了,上面的程序都是反例。異常是程序處理意外情況的機制,當(dāng)程序發(fā)生意外時,我們需要盡可能多的得到意外的信息,包括發(fā)生的位置,描述,原因等等。這些都是我們解決問題的線索。但是上面的例子都只是簡單的printStackTrace()。如果我們自己寫代碼,就要盡可能多的對這個異常進行描述。比如說為什么會出現(xiàn)這個異常,什么情況下會發(fā)生這個異常。如果傳入方法的參數(shù)不正確,告知什么樣的參數(shù)是合法的參數(shù),或者給出一個sample

          3. try block寫的簡短,不要所有的東西都扔在這里,我們盡可能的分析出到底哪幾行程序可能出現(xiàn)異常,只是對可能出現(xiàn)異常的代碼進行try。盡量為每一個異常寫一個try...catch,避免異常丟失。在IO操作中,一個IOException也具有"一夫當(dāng)關(guān)萬夫莫開"的氣魄。

          五.總結(jié)

          總結(jié)非常簡單,不要為了使用異常而使用異常。異常是程序設(shè)計的一部分,對它的設(shè)計也要考究點。





          posted on 2011-12-26 08:57 王康 閱讀(1547) 評論(0)  編輯  收藏

          只有注冊用戶登錄后才能發(fā)表評論。


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 轮台县| 嘉祥县| 安溪县| 本溪市| 临夏市| 铜山县| 忻城县| 河南省| 靖西县| 调兵山市| 长寿区| 理塘县| 靖边县| 沧源| 孙吴县| 文化| 宜君县| 秦安县| 双流县| 井冈山市| 滁州市| 黔东| 腾冲县| 铜山县| 蓝田县| 德兴市| 格尔木市| 平顶山市| 通道| 嘉善县| 榕江县| 通辽市| 兰考县| 兴义市| 木里| 芷江| 股票| 北宁市| 徐州市| 如东县| 永春县|