Java開發筆記(七十六)如何預防異常的產生

每一個程序員都但願本身的程序穩定運行,不要隔三岔五出什麼差錯,但是程序運行時冒出來的各類異常着實煩人,使人不勝其擾。雖然能夠在代碼中補上try/catch語句捕捉異常,但畢竟屬於過後的補救措施。與其後知後覺地亡羊補牢,不如一開始就未雨綢繆,只要防患於未然,必能收到事半功倍的成效。
就編碼時的常見異常而言,絕大多數異常都能經過適當的校驗加以規避,也就是事先指定可以讓程序正常運行的合法條件,只有條件知足纔開展業務邏輯處理,不然進行失敗狀況的處理。這樣用於異常捕捉的try/catch語句便轉換爲了條件分支的if/else語句,對於熟能生巧的if/else流程控制,想必程序員在編碼時更能遊刃有餘。接下來以幾個常見的異常爲例,闡述一下如何預防這些異常的發生。
首先看最簡單的算術異常,若是是除數爲零的異常,檢查一下除數的值是否爲零就好了。若是是大小數除法運算遇到的「商爲無限循環小數」異常,就得在調用divide方法之時指定本次除法運算的小數精度,以及精度範圍最後一位數字的舍入方式。下面是優化後的大小數除法代碼例子:html

	// 測試算術異常:商是無限循環小數
	private static void testDivideByDecimal() {
		BigDecimal one = BigDecimal.valueOf(1);
		BigDecimal three = BigDecimal.valueOf(3);
		// 大小數的除法運算,小數點後面保留64位,其中最後一位作四捨五入
		BigDecimal result = one.divide(three, 64, BigDecimal.ROUND_HALF_UP);
		System.out.println("sqrt result=" + result);
	}

 

其次看數組越界異常,不論是根據下標訪問數組元素,仍是根據索引訪問清單元素,都要保證待訪問元素的下標必須落在數組內部(或索引落在清單內部)。所以,合法的下標數值應當大於零,且小於數組的長度,因而訪問數組元素的代碼可改寫以下:java

	// 測試越界異常:下標超出數組範圍
	private static void testArrayByIndex() {
		int[] array = { 1, 2, 3 };
		// 在根據下標獲取數組元素以前,先判斷該下標是否落在數組範圍以內
		if (array.length > 3) {
			int item = array[3];
			System.out.println("array item=" + item);
		} else {
			System.out.println("array's length isn't more than 3");
		}
	}

 

再次看空指針異常,不管是訪問某對象的實例屬性,仍是調用某對象的實例方法,都要求該對象是真切存在着的,不然面對一個空指針瞎指揮,只能落得竹籃打水一場空的境遇了。在Java編程中,可比較某對象是否等於null來判斷它是否爲空指針,這樣添加了空指針校驗的對象訪問代碼示例以下:程序員

	// 測試空指針異常:對象不存在
	private static void testStringByNull() {
		String str = null;
		// 在跟某位對象約會以前,先打個電話問問有沒空,不然朝思暮想空歡喜一場
		if (str != null) {
			int length = str.length();
			System.out.println("str length=" + length);
		} else {
			System.out.println("str is null");
		}
	}

 

另外還有類型轉換異常,由於一個父類會衍生出許多子類,因此若將父類實例想固然強行轉換爲某個子類,則極可能遭到類型不匹配的失敗。此時爲了確保萬無一失,須要在類型轉換以前增長條件判斷,即利用instanceof檢查該實例是否屬於指定的子類類型,只有類型徹底一致方可進行類型轉換的操做。先作instanceof校驗後作類型轉換的代碼例子以下所示:編程

	// 測試類型轉換異常:原始數據與目標類型不匹配
	private static void testConvertLyList() {
		List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
		// 在作強制類型轉換以前,先把它的底細摸清楚。正所謂「知己知彼,百戰不殆」
		if (list instanceof ArrayList<?>) {
			ArrayList<Integer> arrays = (ArrayList<Integer>) list;
			System.out.println("arrays size=" + arrays.size());
		} else {
			System.out.println("arrays is not belong to ArrayList");
		}
	}

 

其它異常的預防措施大致相似,基本思路是檢驗某項操做的前提條件是否知足,所謂萬變不離其宗,掌握了異常預防的要領便可觸類旁通。數組

因代碼邏輯缺陷而致使的程序異常,能夠經過改進相關業務邏輯加以預防,那麼因系統資源不足而致使的程序錯誤,可否也採起相似的預防措施呢?前面在分析內存溢出和棧溢出錯誤的時候,兩個示例代碼都包含了方法遞歸的過程,這是否意味着,只要不進行遞歸調用,或者只進行有限次的遞歸調用,就能避免撐爆系統的錯誤嗎?從表面上看,前述的內存溢出和棧溢出錯誤,都由無限次的遞歸調用而引起。然而這不能全賴遞歸,無限次遞歸只是產生錯誤的其中一種狀況,事實上還有別的狀況也會形成溢出錯誤,根源在於一個程序分配到的堆內存和棧內存大小是個有限的數值,假若某項業務操做想要佔據一塊很是大的空間,或者某次方法調用須要傳遞一個很是大的參數,那麼沒必要屢次遞歸調用,只需僅僅一次調用就會讓程序癱瘓了。
使用eclipse開發Java的話,默認的堆內存和棧內存大小在eclipse.ini裏面設置,ini文件中的Xmx參數表示JVM最大的堆內存大小,該參數一般配置爲512M;另外一個Xss參數表示每一個線程的棧內存大小,該參數默認爲1M。因此,一旦程序意圖佔用超過512M的內存空間,就會報堆內存溢出的錯誤;一旦某次方法調用須要傳送超過1M大小的參數信息,就會報棧溢出的錯誤。
舉個實際應用的場景,好比在電腦上看電影,如今一個高清電影的視頻大小廣泛有好幾個G,要是將幾G大小的文件全加載進內存才播放,轉眼間電腦內存便所剩無幾。顯然這種作法不可取,合理的方案是邊加載邊播放,每次只要提早加載當前播放位置後面若干秒的視頻,同時釋放掉已經播放完畢的那部分視頻資源,那麼真正用到的內存大小隻有當前位置以及以後的一段視頻緩衝,如此便能在極大程度上節約了內存資源消耗。
再來一個跟方法調用有關的場景,有時調用某方法須要傳遞圖像數據,而位圖格式的圖像體積是至關大的。以一幅分辨率爲800*600的圖片爲例,它共有800*600=48萬個像素,每一個像素又須要8位的灰度、8位的紅色、8位的綠色、8位的藍色加起來是4個字節空間,因而該圖片的位圖數據大小=48萬*4字節=192萬字節≈1.83M,那麼光光這幅圖片的數據便足以耗光程序的棧內存了。想一想你去買房子,一套房子的總價要好幾百萬,難道要拎着數百捆的百元大鈔去付房款嗎?就算麻袋裝得下這幾百捆鈔票,恐怕也沒人拎得動無比沉重的麻袋。正常的作法是把錢存在銀行裏面,而後帶上銀行卡在售樓部直接刷卡,銀行系統就知曉有多少資金髮生了交易。同理,在方法調用時傳遞圖片,無需直接傳送完整的圖像數據,而是先把圖像保存爲某個圖片文件,再向該方法傳遞圖片文件的存儲路徑,這樣下級方法去指定路徑讀取圖片即是。eclipse



更多Java技術文章參見《Java開發筆記(序)章節目錄ide

相關文章
相關標籤/搜索