Java語法與架構中的異常處理(assert斷言、堆棧追蹤)

程序中總有些意想不到的情況所引起的錯誤,Java中的錯誤也以對象方式呈現爲java.lang. Throwable的各類子類實例。只要你能捕捉包裝錯誤的對象,就能夠針對該錯誤作一些處理,例如,試恢復正常流程、進行日誌( Logging)記錄、以某種形式提醒用戶等java

一、使用try、catch 以下小程序,用戶能夠連續輸入整數最後輸入0結束後會顯示輸入數的平均值sql

package errorDemo;
import java.util.*;

public class Average {
	public static void main(String[] args) {
		Scanner scan=new Scanner(System.in);
		double sum = 0;
		int i = 0;
		while(true) {
			int num=scan.nextInt();
			if(num==0) {
				break;
			}
			sum+=num;
			i++;
		}
		System.out.printf("%.1f%n",sum/i);
	}
}

若是用戶不當心輸入錯誤,例如第二個數輸入爲o,而不是0:數據庫

7
o
Exception in thread "main" java.util.InputMismatchException
	at java.base/java.util.Scanner.throwFor(Unknown Source)
	at java.base/java.util.Scanner.next(Unknown Source)
	at java.base/java.util.Scanner.nextInt(Unknown Source)
	at java.base/java.util.Scanner.nextInt(Unknown Source)
	at errorDemo.Average.main(Average.java:10)

這段錯誤信息對除錯是頗有價值的,錯誤信息的第一行:Exception in thread "main" java.util.InputMismatchException Scanner對象的nextInt()方法,能夠將用戶輸入的下一個字符串剖析爲int值,出現InputMismatchException錯誤信息表示不符合 Scanner對象預期,由於下一個字符串自己要表明數字。 Java中全部錯誤都會被打包爲對象後作一些處理。能夠嘗試(try)捕捉(catch)表明錯誤的對象後作一些處理 例如:express

package errorDemo;
import java.util.*;

public class Average {
	public static void main(String[] args) {
		try{
			Scanner scan=new Scanner(System.in);
			double sum = 0;
			int i = 0;
			while(true) {
				int num=scan.nextInt();
				if(num==0) {
				break;
				}
				sum+=num;
				i++;
			}
			System.out.printf("%.1f%n",sum/i);
		}catch(InputMismatchException ex) {
			System.out.println("輸入整數");	
		}
	}
}

這裏使用了try、 catch語法,JVM會嘗試執行try區塊中的程序代碼。若是發生錯誤,執行流程會跳離錯誤發生點,而後比較 catch括號中聲明的類型,是否符合被拋出的錯誤對象類型,若是是的話,就執行 catch區塊中的程序代碼。 執行完畢後進行try,catch以後的代碼。 嘗試恢復正常流程:小程序

package errorDemo;
import java.util.*;

public class Average {
	public static void main(String[] args) {
		Scanner scan = new Scanner(System.in);
		double sum = 0;
		int i = 0;
		while (true) {
			try {
				int num = scan.nextInt();
				if (num == 0) {
					break;
				}
				sum += num;
				i++;
			} catch (InputMismatchException ex) {
				System.out.printf("忽略非整數%s%n",scan.next());
			}
		}
		System.out.printf("%.1f%n", sum / i);
	}
}

若是nextInt()方式錯誤InputMismatchException,程序跳到catch,執行catch區域後,還在while循環,能夠繼續下一個循環流程。(可是並不建議捕捉InputMismatchException) 這裏寫圖片描述 二、異常繼承架構 在先前的 Average範例中,雖然沒有撰寫try、ctch語句,照樣能夠編譯執行。可是這樣撰寫,編譯卻會錯誤:數組

int a =System.in.read();

要解決這個錯誤信息有兩種方式,一是使用try、 catch打包System.in.read();在main()方法旁聲明throws java.io.IOException。簡單來講,編譯程序認爲System. in read()時有可能發生錯誤,要求你必定要在程序中明處理錯誤。 例如,若這樣撰寫就能夠經過編譯:架構

try {
		int a=System.in.read();
	}catch(java.io.IOException ex) {
		ex.printStackTrace();
	}

Average範例與這裏的例子,程序都有可能發生錯誤,編譯程序單單就只要求這裏的範例必定要處理錯誤。要了解這個問題,得先了解那些錯誤對象的繼承架構: 這裏寫圖片描述 首先要了解錯誤會被包裝爲對象,這些對象都是可拋出的(稍後介紹 throw語法,就會了解如何拋出錯誤對象),所以設計錯誤對象都承自java.lang.Throwable類, Throwable定義了取得錯誤信息、堆棧追蹤( (Stack Trace)等方法,它有兩個子類Java. lang.Exception和Java. lang. Error。 Error與其子類實例表明嚴重系統錯誤,如硬件層面錯誤、JVM錯誤或內存不足等問題。雖然也可使用try、 catch來處理 Error對象,但並不建議,發生嚴重系統錯誤時Java應用程序自己是無力回覆的。舉例來講,若JVM所需內存不足,不可能撰寫程序要求操做系統給予JVM更多內存。Error對象拋出時,基本上不用處理,任其傳播至JVM爲止,或者是最多留下日誌信息。 (若是拋出了 Throwable對象,而程序中沒有任何 catch捕捉到錯誤象,最後由JVM捕捉到的話,那JVM基本處理就是顯示錯誤對象打包的信息並中斷程序) 程序設計自己的錯誤,建議使用 Exception或其子類實例來表現,因此一般稱錯誤處理爲異常處理( Exception Handling)。 就語法與繼承架構上來講,若是某個方法聲明會拋出 Throwable或子類實例,只要不是屬於 Error, Java.lang.RuntimeException或其子類實例,就必須明確使用try、 catch語法加以處理,或者用 throws聲明這個方法會拋出異常,不然會編譯失敗。先前調用 System.in.read()時,in實際上是 System的靜態成員,其類型爲java.io. InputStream,若是查詢API文件,能夠看到 Inputstream的read()方法聲明。 IOException是 Exception的直接子類,因此編譯程序要求你明確使用語法加以處理。Exception或其子對象,但非屬於RuntimeException或其子對象,稱爲受檢異常( Checked Exception)。(受編譯程序檢查)受檢異常存在之目的,在於API設計者實現某方法時,某些條件成立時會引起錯誤,並且認爲調用方法的客戶端有能力處理錯誤,要求編譯程序提醒客戶端必須明確處理錯誤,否則不可經過編譯,API客戶端無權選擇要不要處理。( ReflectiveOperationException是JDK7以後新增的類,JDK6以前 ClassNot FoundException等類是直接繼承自 Exception類) 屬於 Runtimeexception衍生出來的類實例,表明API設計者實現某方法時,某些條件成立時會引起錯誤,並且認爲API客戶端應該調用方法前作好檢查,以免引起錯誤之因此命名爲執行時期異常,是由於編譯程序不會強迫必定得在語法上加以處理,亦稱爲非受檢異常( Unchecked Exception) 例如使用數組時,若存取超出索引就會拋出Arrayindexoutofboundsexception 但編譯程序並無強迫你在語法上加以處理。這是由於Arrayindexoutofboundsexception是一種 Runtimeexception,能夠在API文件的開頭找到繼承架構圖。 例如 Average範例中,用戶輸入是否正確並不是事先能夠得知,所以 Inputmismatchexception設計爲一種 Runtimeexception。 Java對於 Runtimeexception的態度是,這是因漏洞而引起,也就是API客戶端調用方法前沒有作好前置檢查纔會引起,客戶端應該修改程序,使得調用方法時不會發生錯誤,若是真要以try、 catch處理,建議是日誌或呈現友好信息,例如以前的 Average範例的做法。 不過前面的 Average範例若要避免出現 Inputmismatchexception應該是取得用戶的字符串輸入以後,檢查是否爲數字格式,如果再轉換爲int整數,若格式不對就提醒用戶作正確格式輸入:app

package errorDemo;
import java.util.Scanner;

public class Average2 {
	public static void main(String[] args) {

		double sum = 0;
		int i = 0;
		while (true) {

			int num = nextInt();
			if (num == 0) {
				break;
			}
			sum += num;
			i++;
		}
		System.out.printf("%.1f%n", sum / i);
	}

	static Scanner scan = new Scanner(System.in);

	static int nextInt() {
		String input = scan.next();
		while (!input.matches("\\d*")) {
			System.out.println("輸入數字");
			input = scan.next();
		}
		return Integer.parseInt(input);
	}
}

上例的 nextInt()方法中,使用了 Scanner的next()方法來取得用戶輸入的下個字符串,若是字符串不是數字格式,就會提示用戶輸入數字, String的 matches()方法中設定了"\d*"這是規則表示式( Regular Expression,),表示檢查字符串中的字符是否是數字,如果則返回true。 用try catch捕捉異常對象時要注意,若是父類異常對象在子類異常對象前被捕捉則catch子類異常對象永遠不會執行,編譯程序會檢查:框架

try {
		int a=System.in.read();
	}catch(Exception ex) {
		ex.printStackTrace();
	}catch(IOException ex) {//編譯錯誤
		ex.printStackTrace();
	}

必須更改順序:網站

try {
		int a=System.in.read();
	}catch(IOException ex) {
		ex.printStackTrace();
	}catch(Exception ex) {
		ex.printStackTrace();
	}

JDK7後可使用多重捕捉語法:

try{
.....
}catch(IOException | InterruptedException | ClassCastIOException e){
	e.printStackTrace();
}

catch()括號內列出異常不得有繼承關係。 三、抓、拋 開發一個連接庫,其中有個功能是讀取純文本文檔,並以字符串返回文檔中全部文字,這麼撰寫:

package errorDemo;
import java.io.*;
import java.util.*;

public class FileUtil {
	public static String readFile(String name) {
		StringBuilder txt=new StringBuilder();
		try {
			Scanner scan=new Scanner(new FileInputStream(name));
			while(scan.hasNext()) {
				txt.append(scan.nextLine()).append('\n');
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
		return txt.toString();
	}
}

scanner建立時能夠給予 Inputstream實例,而 Fileinputstream可指定檔名來開啓與讀取文檔內容,是 Inputstream的子類,所以可做爲Scanner建立之用。因爲建立 Fileinputstream時會拋出 Ellenotfoundexception,根據目前學到的異常處理語法,因而你捕捉 Filenotfoundexception並在控制檯中顯示錯誤信息。 有說這個連接庫會用在文本模式中嗎?若是這個連接庫是用在Web網站上,發生錯誤時顯示在控制檯上,Web用戶怎麼會看獲得?你開發的是連接庫異常發生時如何處理,是連接庫用戶才知道,直接在 catch中寫死處理異常或輸出錯誤信息的方式,並不符合需求。 若是方法設計流程中發生異常,而你設計時並無充足的信息知道該如何處理(例如不知道連接庫會用在什麼環境),那麼能夠拋出異常,讓調用方法的客戶端來處理。例如:

package errorDemo;

import java.io.*;
import java.util.*;

public class FileUtil {
	public static String readFile(String name) throws FileNotFoundException {
		StringBuilder txt = new StringBuilder();
		Scanner scan = new Scanner(new FileInputStream(name));
		while (scan.hasNext()) {
			txt.append(scan.nextLine()).append('\n');
		}

		return txt.toString();
	}
}

操做對象的過程當中若是會拋出受檢異常,但目前環境信息不足以處理異常,沒法使用ty、 catch處理時,可由方法的客戶端依據當時調用的環境信息進行處理。爲了告訴編譯程序這個事實,必須使用 theows聲明此方法會拋出的異常類型或父類型,編譯程序纔會讓你經過編譯。 拋出受檢異常,表示你認爲調用方法的客戶端有能力且應該處理異常, throws聲明部份會是API操做接口的一部份,客戶端不用察看原始碼,從API文件上就能直接得知,該方法可能拋出哪些異常。 若是你認爲客戶端調用方法的時機不當引起了某個錯誤,但願客戶端準備好前置條件,再來調用方法,這時能夠拋出非受檢異常讓客戶端得知此狀況,若是是非受檢異常編譯程序不會要求明確使用try、 catch或在方法上使用 throws聲明,由於Java的設計上認爲,非受檢異常是程序設計不當引起的漏洞,異常應自動往外傳播,不該使用try、 catch來嘗試處理,而應改善程序邏輯來避免引起錯誤。 實際上在異常發生時,可以使用ty、 catch處理當時環境可進行的異常處理,當時環境下沒法決定如何處理的部分,能夠拋出由用方法的客戶端處理。若是想先處理部分事項再拋出,能夠以下:

package errorDemo;

import java.io.*;
import java.util.*;

public class FileUtil {
	public static String readFile(String name) throws FileNotFoundException {
		StringBuilder txt = new StringBuilder();

		try {
			Scanner scan = new Scanner(new FileInputStream(name));
			while (scan.hasNext()) {
				txt.append(scan.nextLine()).append('\n');
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			throw e;
		}
		return txt.toString();
	}
}

在 catch區塊進行完部份錯誤處理以後,可使用 throw(注意不是 throws)將異常再拋出,實際上,你能夠在任何流程中拋出異常,不必定要在 cateh區塊中,在流程中拋出異常,就直接跳離原有的流程,能夠拋出受檢或非受檢異常,若是拋出的是受檢異常,表示你認爲客戶端有能力且應處理異常,此時必須在方法上使用 throws聲明:若是拋出的異常是非受檢異常,表示你認爲客戶端調用方法的時機出錯了,拋出異常是要求客戶端修正這個漏洞再來調用方法,此時也就不使用 throws聲明 若是原先有個方法操做是這樣的:

public class DoSome {
	public static void dosome(String arg) throws FileNotFoundException,EOFException{
		try {
			if("on".equals(arg)) {
				throw new FileNotFoundException();
			}else {
				throw new EOFException();
			}
		}catch(FileNotFoundException e) {
			e.printStackTrace();
			throw e;
		}catch(EOFException e) {
			e.printStackTrace();
			throw e;
		}
	}
}

EOFException FileNotFoundException 都是一種IOException其中zaiJDK6以後能夠直接:

........
catch(IOException e) {
			e.printStackTrace();
			throw e;
		}

若是使用繼承時,父類某個方法聲明throws某些異常,子類從新定義該方法時能夠:

  • 不聲明 throws任何異常
  • throws父類該方法中聲明的某些異常。
  • throws父類該方法中聲明異常的子類。 可是不能夠:
  • thows父類方法中未聲明的其餘異常。
  • throws父類方法中聲明異常的父類

四、注意項 異常處理的本意是,在程序錯誤發生時,可以有明確的方式通知API客戶端,讓客戶端採起進一步的動做修正錯誤,目前Java是惟一採用受檢異常 (Checked Exception)的語言,這有兩個目的:一是文件化,受檢異常聲明會是API操做接口的一部份,客戶端只要查閱文件,就能夠知道方法可能會引起哪些異常,並事先加以處理,而這是API設計者決定是否拋出受檢異常時的考慮之一;二是提供編譯程序信息,讓編譯程序可以在編譯時期就檢查出API客戶端沒有處理異常。問題是有些錯誤發生而引起異常時,你根本無力處理,例如使用JDBC撰寫數據庫聯機程序時,常常要處理的java.sql.Solexception是受檢異常,若是異常的發生緣由是數據庫聯機異常,而聯機異常的緣由是因爲實體線路問題,那麼不管如何你都不可能使用try、 catch處理這種狀況。

錯誤發生時,若是當時情境並無足夠的信息讓你處理異常,能夠就現有信息處理完異常後,從新拋出異常。

public Customer getCustomer(String id) throws SQLException{
	.........
}

上面看起來彷佛沒有問題,但假設這個方法是在整應用程序很是底層被調用,在某個 Sqlexception發生時,最好的方法是將異常浮現至用戶畫面呈現,例如網頁技術,將錯誤信息在網頁上顯示出來給管理人員 爲了讓異常往上浮現,你也許會選擇在每一個法調用上都聲明 throws Solexception,但前面假設,這個方法的調用是在整個應用程序的底層,這樣的作法也許會形成許多程序代碼的修改(更別說要從新編譯了),另外一個問題是,若是你根本無權修改應用程序的其餘部份,這樣的作法顯然行不通。 受檢異常本意良好,有助於程序設計人員注意到異常的可能性並加以處理,但在應用程序規模增大時,會逐漸對維護形成困難,上述狀況不必定是你自定義API時發生,也多是在底層引入了一個會拋出受檢異常的API而發生相似狀況 從新拋出異常時,除了將捕捉到的異常直接拋出,也能夠考慮爲應用程序自定義專屬異常類別,讓異常更能表現應用程序特有的錯誤信息。自定義異常類別時,能夠繼承 Throwable、 Error或Exception的相關子類,一般建議繼承自 Exception或其子類,若是不是繼承自Error或 Runtimeexception,那麼就會是受檢異常。

public class CustomizedException extends Exception{
	 //自定義受檢異常
 }

錯誤發生時,若是當時情境沒有足夠的信息讓你處理異常,你能夠就現有信息處理完異常後,從新拋出異常。既然你已經針對錯作了某些處理,那麼也就能夠考慮自定義異常,用以更精確地表示出未處理的錯誤,若是認爲調用API的客戶端應當有能力處理未處理的錯誤,那就自定義受檢異常、填入適當錯誤信息並從新拋出,並在方法上使用 throws加以聲明;若是認爲調用API的客戶端沒有準備好就調用了方法,纔會形成還有未處理的錯誤,那就自定義非受檢異常、填入適當錯誤信息並從新拋出。

public class CustomizedException extends Runtimeexception{
	//自定義非受檢異常
}

一個基本的例子是這樣的:

try{
 ...
 }catch(SomeException ex){
	 //作些可行的處理
	 // Logging之類的
	 throw new CustomizedException("error message"); // Checked或 Unchecked?
	 }

相似地,若是流程中要拋出異常,也要思考這是客戶端能夠處理的異常仍是客戶端沒有準備好前置條件就調用方法引起的異常.

if(somecondition){
	 throw new CustomizedException("error message");// Checked ak Unchecked?
 }

不管如何,Java採用了受檢異常的作法,Java的標準API彷佛也打算一直這麼區分下去,只是受檢異常讓開發人員無從選擇,會由編譯程序強制性要求處理,確實會在設計上形成麻煩,於是有些開發者在設計連接庫時,乾脆就選擇徹底使用非受檢異常,一些會封裝應用程序底層行爲的框架,如 Spring或 Hibernate,就選擇了讓異常體系是非受檢異常,例如 Spring中的DataAccessException或者是 Hibernate3中的 HibernateException,它們選擇給予開發人員較大的彈性來面對異常(也許也須要開發人員更多的經驗)隨着應用程序的演化,異常也能夠考慮演化,也許一開始是設計爲受檢異常,然而隨着應用程序堆棧的加深,受檢異常總是一層一層往外聲明拋出形成麻煩時,這也許表明了原先認爲客戶端可處理的異常,每一層客戶端實際上都無力處理了,每層客戶端都無力處理的異常,也許該視爲一種漏洞,也許客戶端在呼叫時都該準備好前置條件再行調用,以免引起錯誤,這時將受檢異常演化爲非受檢異常,也許就有其必要。 實際上確實有這類例子, Hibemate2中的 Hibernateexception是受檢異常,然而 Hibernate3 3中的 Hibernateexception變成了非受檢異常 然而,即便不用面對受檢異常與非受檢異常的區別,開發者仍然必須思考,這是客戶端能夠處理的異常仍是客戶端沒有準備好前置條件就調用方法,才引起的異常。 5**、認識堆棧追蹤** 在多重方法調用下,異常發生點多是在某個方法之中,若想得知異常發生的根源以及多重方法調用下異常的堆棧傳播,能夠利用異常對象自動收集的堆棧追蹤( Stack Trace)來取得相關信息。 查看堆棧追蹤最簡單的方法,就是直接調用異常對象的 printstacktrace()。 堆棧追蹤信息中顯示了異常類型,最頂層是異常的根源,如下是調用方法的順序,程序代碼行數是對應於當初的程序原始碼,如使用IDE,單擊行數就會直接開啓原始碼並跳至對應行數(若是原始碼存在的話)。 printstacktrace()還有接受Printstream Printwriter的版本,能夠將堆棧追蹤信息以指定方式至輸出目的地(例如文檔)。 (編譯位碼文檔時,默認會記錄原始碼行數信息等除錯信息,在使用 javac編譯時指定-g:none自變量就不會記錄除錯信息,編譯出來的位碼文檔容量會比較小) 若是想要取得個別的堆棧追蹤元素進行處理,則可使getStackTrace(),這會返回 stacktraceelement數組,數組中索引0爲異常根源的相關信息,以後爲各方法調用中的信息,可使用 StackTraceElement 的 getClassName ()、getFileName()、getLineName ()等方法取得對應的信息。 要善用堆棧追蹤,前提是程序代碼中不可有私吞異常的行爲,例如在捕捉異常後什麼都不作。 這樣的程序代碼會對應用程序維護形成嚴重傷害,由於異常信息會徹底停止,以後調用此片斷程序代碼的客戶端,徹底不知道發生了什麼事,形成除錯異常困難,甚至找不出錯誤根源 另外一種對應用程序維護會有傷害的方式,就是對異常作了不適當的處理,或顯示了不正確的信息。例如,有時因爲某個異常層級下引起的異常類型不少, 有些程序設計人員爲了省麻煩,或由於常常處理找不到文檔的錯誤,於是處理成「找不到文檔」因其餘緣由致使錯誤時依然顯示找不到文檔,就會誤導除錯方向。


在使用throw重拋異常時,異常追蹤堆棧的起點還是異常發生根源,不是重拋異常的地方:

package errorDemo;

public class StackTraceDemo {
	public static void main(String[] args) {
		try {
			c();
		} catch (NullPointerException e) {
			e.printStackTrace();
		}
	}
	private static void c() {
		try {
			b();
		} catch (NullPointerException e) {
			e.printStackTrace();
			throw e;
		}
	}
	private static void b() {
		a();
	}
	private static String a() {
		String txt=null;
		return txt.toUpperCase();
	}
}

拋出錯誤:

java.lang.NullPointerException
	at errorDemo.StackTraceDemo.a(StackTraceDemo.java:24)
	at errorDemo.StackTraceDemo.b(StackTraceDemo.java:20)
	at errorDemo.StackTraceDemo.c(StackTraceDemo.java:13)
	at errorDemo.StackTraceDemo.main(StackTraceDemo.java:6)
java.lang.NullPointerException
	at errorDemo.StackTraceDemo.a(StackTraceDemo.java:24)
	at errorDemo.StackTraceDemo.b(StackTraceDemo.java:20)
	at errorDemo.StackTraceDemo.c(StackTraceDemo.java:13)
	at errorDemo.StackTraceDemo.main(StackTraceDemo.java:6)

若是想讓異常堆棧起點爲重拋異常的地方,可使用fillInStackTrace()方法,這個方法會從新裝填異常堆棧,將起點設爲重拋異常的地方,並返回Throwable對象:

package errorDemo;

public class StackTraceDemo {
	public static void main(String[] args) {
		try {
			c();
		} catch (NullPointerException e) {
			e.printStackTrace();
		}
	}
	private static void c() {
		try {
			b();
		} catch (NullPointerException e) {
			e.printStackTrace();
			Throwable t=e.fillInStackTrace();
			throw (NullPointerException)t;
		}
	}
	private static void b() {
		a();
	}
	private static String a() {
		String txt=null;
		return txt.toUpperCase();
	}
}

拋出錯誤:

java.lang.NullPointerException
	at errorDemo.StackTraceDemo.a(StackTraceDemo.java:25)
	at errorDemo.StackTraceDemo.b(StackTraceDemo.java:21)
	at errorDemo.StackTraceDemo.c(StackTraceDemo.java:13)
	at errorDemo.StackTraceDemo.main(StackTraceDemo.java:6)
java.lang.NullPointerException
	at errorDemo.StackTraceDemo.c(StackTraceDemo.java:16)
	at errorDemo.StackTraceDemo.main(StackTraceDemo.java:6)

六、關於assert 有時候,需求或設計時就可確認,程序執行的某個時間點或某個狀況下,必定是處於或不處於何種狀態,若不是,則是個嚴重錯,開發過程當中發現這種嚴重錯誤,必須立中止程序確認需求與設計 程序執行的某個時間點或某個狀況下,必然處於或不處於何種狀態,這是一種斷言( Assertion),例如某個時間點程序某個變量值必定是多少。斷言的結果必定是成立或不成立,預期結果與實際程序狀態相同時,斷言成立,不然斷言不成立。 Java在JDK1.4以後提供 assert語法,有兩種使用的語法

assert boolean_expression;
 assert boolean_expression:detail_expression;

boolean_expression若爲true,則什麼事都不會發生,若是爲 false,則會發生java.lang. AssertionError,此時若採起的第二個語法,則會將 detail expression的結果顯示出來,若是當中是個對象,則調用 toString ()顯示文字描述結果. 斷言功能是在JDK1.4以後提供,因爲使用 assert做爲關鍵字,爲了不JDK1.3或更早版本程序使用assert做爲變量致使名稱衝突問題,默認執行時不啓動斷言檢查。若是要在執行時啓動斷言檢查,能夠在執行java指令時,指定- -enableassertions或是-ea自變量。那麼什麼時候該使用斷言:

  • 斷言客戶端調用方法前,已經準備好某些前置條件(一般在 private方法之中)
  • 斷言客戶端調用方法後,具備方法承諾的結果。
  • 斷言對象某個時間點下的狀態
  • 使用斷言取代批註
  • 斷言程序流程中絕對不會執行到的程序代碼部份
public void charge(int money) {//取錢調用
		if(money<0) {
			if(money<this.balance) {
				this.balance-=money;
			}else {
			System.out.println("錢不夠");
			}
		}else {
			System.out.println("error");
		}
	}

原先的設計在錯誤發生時,直接在控制檯中顯示錯誤信息,適當地將 charge法中的子流程封裝爲方法調用,並將錯誤信息以例外拋出,原程序可修改以下:

public void charge(int money) throws InsufficientException {
	checkGreater(money);
	checkBalance(money);
	this.balance-=money;
}
private void checkGreater(int money){
	if(money<0) {
		throw new IllgalArgumentException("error");
	}
}
private void checkBalance(int money) throws InsufficientException{
	if(money>this.balance) {
		throw new InsufficientException("錢不夠",this.balance);
		}
}

這裏假設餘額不足是種商務流程上可處理的錯誤,所以讓Insufficientexception繼承自 Exception成爲受檢例外,要求客戶端呼叫時必處理,而調用 charge方法時,原本就不應傳入負數,所以checxgrater()會拋出非受檢Illegalargumentexception,這是種讓錯的程序看得出錯,藉由防護式程序設計( Defensive Programmig),來實現速錯(Failas)概念。 實際上, checkgreater()是一種前置條件檢查,若是程序上線後就再也不須要這種檢查的話,能夠將之以 assert取代,並在開發階段使用-ea選項,而程序上線後取消該選項。使用assert:

public void charge(int money) throws InsufficientException {
	assert money>=0 : "error";

	checkGreater(money);
	checkBalance(money);
	this.balance-=money;
	
	assert this.balance>=0 : "this.balance不能是負數";
}

private void checkBalance(int money) throws InsufficientException{
	if(money>this.balance) {
		throw new InsufficientException("錢不夠",this.balance);
		}
}

另一種用assert的狀況,必定不能有default的情況:

...
switch(...){
	case ...:
	...
	break;
	........

	default:
	assert false:"非定義常數";//不可讓default情況發生。default情況發生意味着開發時期嚴重錯誤
}
相關文章
相關標籤/搜索