Java開發筆記(七十三)常見的程序異常

一個程序開發出來以後,不管是用戶仍是程序員,都但願它穩定地運行,然而程序畢竟是人寫的,人無完人哪能不犯點錯誤呢?就算事先考慮得完美無缺,揣着一筆鉅款跑去島國買了棟抗震性能良好的海邊別墅,誰料人算不如天算,碰到猴年馬月趕上了一場大海嘯,整個別墅被衝到山上去了。計算機程序也是如此,不論是人爲的錯誤,仍是意外的風險,都會致使程序在運行時異常退出。引發程序異常的緣由多種多樣,就已經介紹過的知識點而言,主要有這麼幾種可能發生異常的狀況:數學運算異常、數組越界異常、字符串與日期格式異常、空指針異常、類型轉換異常等等,接下來分別進行詳細說明。html

一、數學運算異常
最多見的算術異常當爲除數爲零,衆所周知,在除法運算中除數是不能爲零的,縱使數學家規定一除以零的結果等於無窮大,但是計算機該如何表達無窮大呢?要知道我的電腦的內存總共才幾個G。既然有限的內存容納不了無限的大小,想讓程序計算一除以零就是不可能的事情了。接下來不妨經過一個除數爲零的Java程序驗證看看,測試用的方法代碼示例以下:java

	// 測試算術異常:除數爲0
	private static void testDivideByZero() {
		int one = 1;
		int zero = 0;
		int result = one / zero;
		System.out.println("divide result="+result);
	}

 

運行以上的測試代碼,果不其然觀察到了異常日誌「java.lang.ArithmeticException: / by zero」,可見除數爲零是不正確的寫法。
另外一種算術異常也跟無限有關,像一除以三的結果爲三分之一,使用小數表達的話即是0.33333333……這樣的無限循環小數。固然因爲浮點類型和雙精度類型有精度限制,所以使用浮點數抑或雙精度數存放三分之一,都只會精確到小數點後若干位,並不存在無限循環的問題。麻煩出在大小數BigDecimal上面,由於大小數默認是絕對精確的,只要開發者不指定大小數的精度位數,則系統會竭盡所能把大小數的精確值原本來本地表達出來。那麼問題就來了,三分之一的數值乃無限循環小數,小數點後面的3有無限多個,似此無限的位數,依舊讓有限的內存徒呼奈何。下面即是經過大小數計算一除以三的代碼例子:程序員

	// 測試算術異常:商是無限循環小數
	private static void testDivideByDecimal() {
		BigDecimal one = BigDecimal.valueOf(1);
		BigDecimal three = BigDecimal.valueOf(3);
		BigDecimal result = one.divide(three);
		System.out.println("sqrt result="+result);
	}

 

運行上面的除法代碼,可見程序仍然打印了異常日誌「java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.」,意思是無限小數無法用精確的十進制數來表達。數組

二、數組越界異常
假設某個數組只有三個元素,正常狀況可以訪問第一個、第二個和第三個元素,要是程序強行訪問第四個元素,系統該怎麼辦?總不能無中生有變戲法變出一個吧,計算機程序可不是魔術師,它找不到第四個元素就崩潰退出了。好比如下的數組訪問代碼就重現了這個問題:ide

	// 測試越界異常:下標超出數組範圍
	private static void testArrayByIndex() {
		int[] array = {1, 2, 3};
		int item = array[3];
		System.out.println("array item="+item);
	}

 

運行以上的測試代碼,程序果真輸出了異常信息「java.lang.ArrayIndexOutOfBoundsException: 3」,此處的下標3表明數組的第四個元素,而該數組總共只有三個元素。
不光是數組存在越界異常,容器裏的清單List也存在一樣的問題,由於清單的索引相似數組的下標,一旦尋求訪問的元素索引超出了清單大小,程序運行時也會扔出數組越界異常。用於演示經過索引訪問清單元素的代碼示例以下:工具

	// 測試越界異常:索引超出清單範圍
	private static void testListByIndex() {
		List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
		Integer item = list.get(5);
		System.out.println("list item="+item);
	}

 

運行上述的清單訪問代碼,依然可見程序扔出的異常描述「java.lang.ArrayIndexOutOfBoundsException: 5」,表示索引爲5的位置已經超出了當前數組(實際上是清單)的邊界。性能

三、字符串與日期格式異常
調用String類的format方法進行字符串格式化之時,每種格式定義與數據類型是一一對應的,例如%d對應整型數,%s對應字符串,%b對應布爾值等等。因此格式化的參數值必須和它的格式要求相符,假若兩者匹配不了,這可如何是好?譬如原先定義的參數格式爲%d,表示此處指望格式化一個整型數,結果後面的參數列表卻傳入某個字符串,難道字符串要格成整數?恐怕只能讓程序嗝屁了。不信請看下面的字符串格式化代碼:測試

	// 測試格式異常:字符串格式非法
	private static void testStringByFormat() {
		String str = String.format("%d", "Hello");
		System.out.println("str="+str);
	}

 

運行上面的格式化代碼,毫無疑問程序沒法正常運行,只能無奈地打印異常日誌「java.util.IllegalFormatConversionException: d != java.lang.String」。
不僅僅字符串有格式要求,日期時間也有格式要求,若是須要把日期數據轉換成字符串類型,就得在構造SimpleDateFormat實例時書寫正確的時間格式,一個字很少一個字很多,假若把分鐘格式mm誤寫爲mi,試試看程序會怎麼運行如下的時間轉換代碼?spa

	// 測試格式異常:日期格式非法
	private static void testDateByFormat() {
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mi:ss");
		String strDate = sdf.format(new Date());
		System.out.println("strDate="+strDate);
	}

 

因爲時間格式指定的分鐘代號mi有誤(正確的應爲mm),所以運行以上的測試代碼,程序也只能乖乖地打印出錯信息「java.lang.IllegalArgumentException: Illegal pattern character 'I'」,表示mi裏面的字母I是非法的格式字符。指針

四、空指針異常
面向對象的前提是有這個對象,比如這個春節你媽喊你帶上對象回家過年,可要是對象連影都見不着,你媽給你對象準備的噓寒問暖就都泡湯了。在Java代碼裏面,除了少數幾個基本類型,其他絕大多數類型必須先給對象建立實例,而後才能訪問該對象的各項成員屬性和成員方法。假如不給對象分配實例,就想牽起對象的小手,系統會果斷地告訴你:門都沒有!譬如經常使用的字符串類型,不論是new出一個字符串實例,仍是硬塞給它一個雙引號括起來的具體串,都算做分配了對象實例。若是聲明字符串對象時啥都不幹,或者隨便填了個null,那真是對不起了,程序認爲該對象沒有初始化,就不會給它分配存儲空間。後面的代碼再想操做這個對象的時候,找不到對象地址只能報空指針異常了,有關的異常重現代碼以下所示:

	// 測試空指針異常:對象不存在
	private static void testStringByNull() {
		String str = null;
		int length = str.length();
		System.out.println("str length="+length);
	}

 

運行如上的測試代碼,觀察到打印的異常信息爲「java.lang.NullPointerException」,顯然被系統揪到了偷懶的小辮子。

五、類型轉換異常
在運用多態技術的時候,經常將某個父類實例轉換成子類的類型,以便調用子類自身的方法。但這得確保原來的父類實例來自於該子類才行,倘使父類實例來自另外一個子類B,代碼卻想把它強行轉換爲子類A,也就是俗稱的張冠李戴,系統天然不容許這種胡攪蠻纏的狀況。儘管開發者通常不會糊塗,但是難保偶爾腦殼抽筋,好比數組工具Arrays的asList方法返回一個清單對象,乍看過去與列表類型ArrayList是一個傢伙,誰知真要轉換類型的話,程序竟然會不認帳!這裏轉換清單類型的代碼示例以下:

	// 測試類型轉換異常:原始數據與目標類型不匹配
	private static void testConvertLyList() {
		List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
		ArrayList<Integer> arrays = (ArrayList<Integer>) list;
		System.out.println("arrays size="+arrays.size());
	}

 

運行上述的類型轉換代碼,結果輸出異常日誌「java.lang.ClassCastException: java.util.Arrays$ArrayList cannot be cast to java.util.ArrayList」,沒想到此列表非彼列表,當真是大意不得。



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

相關文章
相關標籤/搜索