程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> Java編程手冊—異常處理與斷言

Java編程手冊—異常處理與斷言

編輯:JAVA綜合教程

Java編程手冊—異常處理與斷言


1. 異常處理
1.1 引言
異常是程序執行過程中產生的異常事件,它會打斷正常的程序執行流,因此,在程序執行過程中就會發生異常,例如,你可能期望用戶可以輸入一個整數,但是輸出的卻是一個String的文本或者在運行時發生其他的一些沒有預料到的錯誤都會導致異常的發生,最主要的問題是這些異常發生後我們該怎麼辦?總而言之就是我們如何去處理這些異常。如果這些異常沒有被適當的處理,程序可能就會中斷並且導致比較嚴重的後果,例如,網絡連接,數據庫連接或者文件在打開之後沒有來得及關閉就發生異常,可能就會導致數據庫或者文件記錄丟失。

Java提供了一個內建的機制去處理這些運行時異常,叫做異常處理,它確保你可以書寫健壯性比較強的代碼。
像C語言這種比較古老的語言就在異常處理上面存在一些缺陷,例如,假設程序員像打開一個文件進行處理。
1、程序員在寫代碼的時候可能不會意識到異常的發生情況,例如,打開的文件可能不存在,因此,程序員可能不會在打開文件之前檢測文件是否存在。
2、假設程序員意識到了異常的發生的情況,他可能想著會先寫主邏輯代碼,然後再考慮異常,但是往往寫完主邏輯代碼之後就會忘記寫異常處理的代碼了。

3、假設程序員考慮到了寫異常處理代碼,異常處理代碼可能會使用if-elses語句跟主邏輯代碼混合在一塊,這就使得主邏輯比較混亂,程序閱讀性差。例如:

 

if (file exists) {
   open file;
   while (there is more records to be processed) {
      if (no IO errors) {
         process the file record
      } else {
         handle the errors
      }
   }
   if (file is opened) close the file;
} else {
   report the file does not exist;
}

java克服了上面這些缺陷,它將異常的處理內置到語言之中,而不是讓他們去限制程序員。
1、如果調用某個方法會發生異常,編譯器會提醒程序員進行異常處理。因為在方法聲明的時候也會聲明相應的異常。
2、程序員會被編譯器強制進行異常處理,否則編譯會不通過,這樣程序員就不會忘記進行異常的處理了。
3、異常處理代碼和主邏輯代碼是分開的,使用的就是try-catch-finally結構。

 

 

下面我們來詳細說說這三個點:
1、異常必須被聲明
例如,假設你想使用java.util.Scanner得到硬盤文件格式化的輸入,Scanner構造函數如下:

public Scanner(File source) throws FileNotFoundException;
它聲明了該方法可能會產生文件不存在的異常,通過在方法上聲明該異常,這樣在使用這個方法的時候,程序員就需要處理這個異常。

2、異常必須被處理
如果方法上面聲明了一個異常,那麼你在使用這個方法的時候就必須處理這個異常,否則編譯不通過。
例如:
import java.util.Scanner;
import java.io.File;
public class ScannerFromFile {
   public static void main(String[] args) {
      Scanner in = new Scanner(new File("test.in"));
      // do something ...
   }
}
上面代碼沒有處理異常,編譯器會提示如下錯誤:
ScannerFromFile.java:5: unreported exception java.io.FileNotFoundException; must be caught or declared to be thrown
      Scanner in = new Scanner(new File("test.in"));
                   ^

如果你調用的某個方法可能會拋出異常,你必須進行如下處理:
(1)提供異常處理代碼,使用try-catch或者try-catch-finally結構。
(2)在當前方法中對異常不進行處理,但是在該方法上需要聲明將異常拋到調用棧中的下一個更高級別的方法中去處理。

例如:try-catch或者try-catch-finally結構處理

 

 

import java.util.Scanner;
import java.io.File;
import java.io.FileNotFoundException;
public class ScannerFromFileWithCatch {
   public static void main(String[] args) {
      try {
         Scanner in = new Scanner(new File("test.in"));
         // do something if no exception ...
         // you main logic here in the try-block
      } catch (FileNotFoundException ex) { // error handling separated from the main logic
         ex.printStackTrace();             // print the stack trace
      }
   }
}
如果沒有找到文件,異常就會在catch塊中產生,在上面例子中它只是打印出了棧信息,它提供了有用的調試信息,在有些情況下,你需要做一些清除操作,或者打開另一個文件,可以看到上面的處理邏輯與主邏輯是分開的。

例如:將異常拋向調用棧的下一個更高級別的方法中去。
import java.util.Scanner;
import java.io.File;
import java.io.FileNotFoundException;
public class ScannerFromFileWithThrow {
   public static void main(String[] args) throws FileNotFoundException {  
                                 // to be handled by next higher-level method
      Scanner in = new Scanner(new File("test.in"));   
                                // this method may throw FileNotFoundException
      // main logic here ...
   }
}
上面這個例子中,並沒有使用try-catch塊去處理FileNotFoundException異常,取而代之的是在它的調用方法main上面去對這個異常進行了聲明,這就意味著這個異常會拋到調用棧中下一個更高級別的方法中去,在這個例子中,main()的下一個更高級別的方法是JVM,它會簡單的終止程序並且打印棧信息。

3、主邏輯與異常處理邏輯是分開的
在上面的例子中,主邏輯程序是放在try塊中,異常處理程序是放在catch塊中,很明顯它們是分開的,這很大程度上提高了程序的可閱讀性。例如:

 

 

try {
   // Main logic here
   open file;
   process file;
   ......
} catch (FileNotFoundException ex) {   // Exception handlers below
   // Exception handler for "file not found"
} catch (IOException ex) {
   // Exception handler for "IO errors"
} finally {
  close file;      // always try to close the file
}

1.2 方法調用棧
對於應用方法調用的級別是使用方法調用棧進行管理,方法調用棧是一個先進後出的隊列,在下面的例子中,main()方法調用methodA(); methodA() 調用 methodB(); methodB() 調用 methodC().

 

 

public class MethodCallStackDemo {
   public static void main(String[] args) {
      System.out.println("Enter main()");
      methodA();
      System.out.println("Exit main()");
   }
 
   public static void methodA() {
      System.out.println("Enter methodA()");
      methodB();
      System.out.println("Exit methodA()");
   }
 
   public static void methodB() {
      System.out.println("Enter methodB()");
      methodC();
      System.out.println("Exit methodB()");
   }
 
   public static void methodC() {
      System.out.println("Enter methodC()");
      System.out.println("Exit methodC()");
   }
}
Enter main()
Enter methodA()
Enter methodB()
Enter methodC()
Exit methodC()
Exit methodB()
Exit methodA()
Exit main()

從上面的輸出中可以看到
1、JVM調用 main()方法。
2、main()在調用methodA()方法之前入棧。
3、methodA()在調用methodB()之前入棧。
4、methodB()在調用methodC()之前入棧。
5、methodC()完成。
6、methodB()出棧並且執行完成。
7、methodA()出棧並且執行完成。
8、main()出棧並且執行完成,程序退出。

\
假設我們在methodC()中執行除以0的操作,它會引發一個ArithmeticException異常。

public static void methodC() {
   System.out.println("Enter methodC()");
   System.out.println(1 / 0);  // divide-by-0 triggers an ArithmeticException
   System.out.println("Exit methodC()");
}
異常消息很清楚的顯示了方法調用棧信息並且還是方法的行數。

 

 

Enter main()
Enter methodA()
Enter methodB()
Enter methodC()
Exception in thread "main" java.lang.ArithmeticException: / by zero
        at MethodCallStackDemo.methodC(MethodCallStackDemo.java:22)
        at MethodCallStackDemo.methodB(MethodCallStackDemo.java:16)
        at MethodCallStackDemo.methodA(MethodCallStackDemo.java:10)
        at MethodCallStackDemo.main(MethodCallStackDemo.java:4)

MethodC()
觸發了一個ArithmeticException異常,因為它沒有處理這個異常,因此它就會立即出棧,MethodB()也不能處理這個異常並且它也會出棧, methodA() 和 main()也相同,main()會返回到JVM,它會終止程序並且向上面一樣打印棧信息。

 

1.3 異常與調用棧

當java方法中出現一個異常,這個方法會創建一個Exception對象,並且傳遞這個Exception對象到JVM,Exception對象包含了異常的類型以及異常發生時程序的狀態,JVM的責任就是尋找處理這個Exception對象的異常處理者,它在調用棧中不斷的向後搜索直到找到一個匹配的異常處理者來處理這個Exception對象,如果JVM在方法棧的所有方法中沒有找到一個匹配的異常處理者,它就會終止程序。

整個過程可以想象如下:假設methodD()發生了一個異常事件並且向JVM拋出了一個XxxException,JVM會在調用棧中向後搜索匹配的異常處理者,它一步一步沿著調用棧向後尋找,發現methodA()有一個XxxException處理者,並且會將這個異常對象傳遞給這個異常處理者者。這裡需要注意的是,methodC() 和 methodB()被遍歷到了,只是它們沒有對異常處理,所以它們的方法會聲明一個throws XxxException。

\

 

1.4 異常類 - Throwable, Error, Exception & RuntimeException
下圖顯示了Exception類的繼承圖,所有異常對象的基類是java.lang.Throwable,它包含有兩個子類,java.lang.Exception 和 java.lang.Error。

\

Error表示系統內部異常(例如:VirtualMachineError, LinkageError),這種異常一般很少發生,如果這種異常發生,你基本上無法處理,程序會在終止。
Exception表示程序異常(例如:FileNotFoundException, IOException),這種異常可以被捕獲並且處理。


1.5 可檢查與不可檢查異常
我們都知道Error的子類以及RuntimeException是不可檢查異常,這些異常是編譯器無法檢查的,因此它們不需要被捕捉或者在方法中聲明,因為這些異常例如除以0會導致一個ArithmeticException,索引越界會導致ArrayIndexOutOfBoundException都是程序的邏輯錯誤,都是可以避免的,應該在程序中進行修正而不是進行運行時異常處理。
其他的異常都是可檢查的異常,它們可以被編譯器檢查到,並且進行捕獲或者在方法中聲明拋出。

1.6 異常處理操作
在異常處理中有5個關鍵字:try, catch, finally, throws 和 throw,需要注意throws 和 throw的不同。
Java的異常處理包含以下三種操作:
(1)聲明異常
(2)拋出異常
(3)捕獲異常

聲明異常
Java方法必須對可檢查異常進行聲明,使用關鍵字throws。
例如,假設methodD()定義如下:

 

public void methodD() throws XxxException, YyyException {
   // method body throw XxxException and YyyException
}
上面方法表示在調用methodD()可能會遇到兩種可檢查異常:XxxException 和 YyyException,換句話說,methodD()方法的內部可能會觸發XxxException 或者 YyyException。

Error, RuntimeException以及RuntimeException的子類不需要被聲明,它們是不可檢查異常,因為它們是編譯器無法檢查到的。

 

拋出異常
當一個Java操作遇到一個異常,出現錯誤的語句可以創建一個指定的Exception對象並且通過throw XxxException語句將它拋給Java運行時。

例如:

 

public void methodD() throws XxxException, YyyException {   // method's signature
   // method's body
   ...
   ...
   // XxxException occurs
   if ( ... )
      throw new XxxException(...);   // construct an XxxException object and throw to JVM
   ...
   // YyyException occurs
   if ( ... )
      throw new YyyException(...);   // construct an YyyException object and throw to JVM
   ...
}

需要注意的是,在方法上聲明異常的關鍵字是throws,在方法體中拋出一個異常對象的關鍵字是throw。

捕獲異常
當一個方法拋出一個異常,JVM會在調用棧中向後搜索來尋找匹配的異常處理者,每個異常處理者可以處理一個指定的異常類,一個異常處理者可以處理一個指定的異常類以及它的子類。如果在方法調用棧中沒有找到指定的異常處理者,程序就會終止。

例如,假設methodD()聲明可能拋出XxxException 和 YyyException異常。
public void methodD() throws XxxException, YyyException { ...... }

在methodC()中調用methodD()方法可能出現以下情形:
(1)在調用methodD()的時候,使用try-catch (或者 try-catch-finally)語句去包裹methodD()。每個catch塊都包含有一種類型的異常處理者。

 

 

public void methodC() {  // no exception declared
   ......
   try {
      ......
      // uses methodD() which declares XxxException & YyyException
      methodD();
      ......
   } catch (XxxException ex) {
      // Exception handler for XxxException
      ......
   } catch (YyyException ex} {
      // Exception handler for YyyException
      ......
   } finally {   // optional
      // These codes always run, used for cleaning up
      ......
   }
   ......
}

(2)假設調用methodD()的方法methodC()不想通過try-catch去處理異常,它可以聲明將異常拋給調用棧的下一個方法。

 

 

public void methodC() throws XxxException, YyyException {   // for next higher-level method to handle
   ...
   // uses methodD() which declares "throws XxxException, YyyException"
   methodD();   // no need for try-catch
   ...
}
在上面這個例子中,如果XxxException 或者 YyyException被methodD()拋出,JVM就會終止methodD()並且methodC()會將異常傳遞給調用棧中methodC()的調用者。

1.7 try-catch-finally

try-catch-finally的語法如下:

try {
   // main logic, uses methods that may throw Exceptions
   ......
} catch (Exception1 ex) {
   // error handler for Exception1
   ......
} catch (Exception2 ex) {
   // error handler for Exception1
   ......
} finally {   // finally is optional
   // clean up codes, always executed regardless of exceptions
   ......
}
(1)如果在執行try塊中的內容的時候沒有異常發生,所有catch塊中的邏輯將會跳過,在執行try塊邏輯之後finally塊將會執行。如果try塊中拋出一個異常,Java運行時就會不顧try塊後面的內容直接跳到catch塊中去尋找對應的異常處理者,它會匹配每個catch塊中的異常類型,如果某個catch塊的異常類型跟拋出異常的類型相同或者是拋出異常類型的父類,那麼匹配成功,這個catch就會執行,在執行完catch塊內容執行接著就會執行finally塊裡面的邏輯,執行完了finally塊裡面的邏輯之後就會接著執行後面的邏輯了。
(2)如果沒有匹配的catch塊,異常就會在調用棧中傳給該方法的調用者,當前的方法會執行finally塊並且退出調用棧。
值得注意的是不管異常是否發生finally塊總是會執行的。除非JVM遇到了嚴重的錯誤或者在catch塊中執行了System.exit()方法。

例子1
import java.util.Scanner;
import java.io.File;
import java.io.FileNotFoundException;
public class TryCatchFinally {
   public static void main(String[] args) {
      try {         // main logic
         System.out.println("Start of the main logic");
         System.out.println("Try opening a file ...");
         Scanner in = new Scanner(new File("test.in"));
         System.out.println("File Found, processing the file ...");
         System.out.println("End of the main logic");
      } catch (FileNotFoundException ex) {    // error handling separated from the main logic
         System.out.println("File Not Found caught ...");
      } finally {   // always run regardless of exception status
         System.out.println("finally-block runs regardless of the state of exception");
      }
      // after the try-catch-finally
      System.out.println("After try-catch-finally, life goes on...");
   }
}
當FileNotFoundException被觸發的時候輸出如下:
Start of the main logic
Try opening a file ...
File Not Found caught ...
finally-block runs regardless of the state of exception
After try-catch-finally, life goes on...

當沒有異常發生的時候,輸出如下:
Start of the main logic
Try opening a file ...
File Found, processing the file ...
End of the main logic
finally-block runs regardless of the state of exception
After try-catch-finally, life goes on...

例子2

public class MethodCallStackDemo {
   public static void main(String[] args) {
      System.out.println("Enter main()");
      methodA();
      System.out.println("Exit main()");
   }
 
   public static void methodA() {
      System.out.println("Enter methodA()");
      try {
         System.out.println(1 / 0);
            // A divide-by-0 triggers an ArithmeticException - an unchecked exception
            // This method does not catch ArithmeticException
            // It runs the "finally" and popped off the call stack
      } finally {
         System.out.println("finally in methodA()");
      }
      System.out.println("Exit methodA()");
   }
}

執行結果:
Enter main()
Enter methodA()
finally in methodA()
Exception in thread "main" java.lang.ArithmeticException: / by zero
        at MethodCallStackDemo.methodA(MethodCallStackDemo.java:11)
        at MethodCallStackDemo.main(MethodCallStackDemo.java:4)

try-catch-finally
(1)try塊必須伴隨著至少一個catch塊或者一個finally塊。
(2)可以有多個catch塊,每個catch塊對應一種異常類型。
(3)catch塊中包含了一個參數,它是拋出的throwable對象,例如:
catch (AThrowableSubClass aThrowableObject) {
   // exception handling codes
}
(4)對於catch塊中的參數throwable對象,可以使用下面方法來得到異常的類型和程序的調用狀態:
printStackTrace():打印Throwable和它的調用棧信息,第一行就是異常的類型,後面的就是帶有行數的方法的調用棧信息。
try {
   Scanner in = new Scanner(new File("test.in"));
   // process the file here
   ......
} catch (FileNotFoundException ex) {
   ex.printStackTrace();
}
也可以使用printStackTrace(PrintStream s) 或者 printStackTrace(PrintWriter s).
getMessage(): 如果異常對象使用的是throwable(String message)構造函數,返回異常對象指定的message。 toString(): 返回Throwable對象的短描述,它包含了"類名"+":"+getMessage()。 (5)catch塊捕捉指定類型或者指定類型的子類型的異常對象。因此catch(Exception ex) {...}可以捕捉所有類型的異常,但是這種沒有目的性的進行異常的捕捉並不是比較好的異常處理方法。
(6)catch塊的順序非常重要,子類異常必須在父類異常的前面,因為異常的匹配是從上到下進行的,如果子類異常在父類異常的後面,可以子類異常就沒有作用了,因為它都被父類異常捕捉了,編譯器也會報出"exception XxxException has already been caught"。
(7)finally塊主要執行一些清除操作,例如文件的關閉,另外finally是總會執行的,除非catch塊提前手動的結束當前方法。

如果對於異常並不關心
一般這個是不建議的,但是做法可以是在main方法和其他方法上使用"throws Exception"語句聲明。
public static void main(String[] args) throws Exception {  // throws all subclass of Exception to JRE
   Scanner in = new Scanner(new File("test.in"));   // declares "throws FileNotFoundException"
   ......
   // other exceptions
}
方法的重新和重載
(1)重寫方法必須有相同的參數列表和返回類型,重載方法必須有不同的參數列表,但是它可以有任意的返回類型。
(2)重寫方法時不能添加有更多的訪問限制,例如,protected的方法,重寫的時候可以為protected或者public但是不能是private或者default,因為重寫方法是為了覆蓋原來的方法,因此不能有更多的限制。
(3)如果原始方法沒有聲明異常類型,那麼重寫這個方法不能聲明異常類型,如果原始方法聲明了異常類型,重寫這個方法可以聲明該類型或者該類型的子類型。
(4)重載方法必須有不同的參數列表,如果僅僅只是在返回類型,異常,或者限定符上不同,都是不合法的,當參數列表不同的時候,它可以有任意的返回類型,訪問限制或者異常。

1.8 常用異常類

ArrayIndexOutOfBoundsException:如果訪問的數組索引超出了數組的限制,就會被JVM拋出。
int[] anArray = new int[3];
System.out.println(anArray[3]);
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 3
NullPointerException:當代碼嘗試使用一個對象的空引用的時候,就會被JVM拋出。
String[] strs = new String[3];
System.out.println(strs[0].length());
Exception in thread "main" java.lang.NullPointerException
NumberFormatException: 當嘗試將一個字符串轉換為一個數字類型,但是字符串沒有合適的轉換方法。
Integer.parseInt("abc");
Exception in thread "main" java.lang.NumberFormatException: For input string: "abc"
ClassCastException: 當對象類型轉換失敗的時候,就會被JVM拋出。
Object o = new Object();
Integer i = (Integer)o;
Exception in thread "main" java.lang.ClassCastException: java.lang.Object cannot be cast to java.lang.Integer
IllegalArgumentException:當一個方法接受到一個非法或者不合適的參數的時候就會拋出,我們可以在自己的代碼中重用這個異常。
IllegalStateException: 最典型的例子就是當可能某個方法只允許調用一次,但是被嘗試多次調用,也就是當某個方法被調用,但是當前程序對於這個任務的執行並不在一個合適的狀態下,就會拋出這個異常。
NoClassDefFoundError: 當某個定義的類沒有被找到,就會被拋出。在JDK 1.7之前,如果你嘗試訪問一個不存在的類,就會看到看到這個異常調用棧信息,在JDK 1.7之後,就只會打印簡要的錯誤信息:"Error: Could not find or load main class xxx"。
1.9 自定義異常類 你可以使用JDK中提供的異常類,例如:IndexOutOfBoundException, ArithmeticException, IOException, 或者 IllegalArugmentException,另外,你可以通過擴展Exception或者它的子類來創建自己的異常類。

值得注意的是,RuntimeException和它的子類是編譯器不可檢查的類,所以它們不需要在方法中聲明,因此使用它們的時候應該小心,因為它們是由於不好的編程習慣造成的,是可以避免的。

例子:
// Create our own exception class by subclassing Exception. This is a checked exception
public class MyMagicException extends Exception {
   public MyMagicException(String message) {  //constructor
      super(message);
   }
}

public class MyMagicExceptionTest {
   // This method "throw MyMagicException" in its body.
   // MyMagicException is checked and need to be declared in the method's signature 
   public static void magic(int number) throws MyMagicException {
      if (number == 8) {
         throw (new MyMagicException("you hit the magic number"));
      }
      System.out.println("hello");  // skip if exception triggered
   }
   
   public static void main(String[] args) {
      try {
         magic(9);   // does not trigger exception
         magic(8);   // trigger exception
      } catch (MyMagicException ex) {   // exception handler
         ex.printStackTrace();
      }
   }
}
輸出結果:
hello
MyMagicException: you hit the magic number
        at MyMagicExceptionTest.magic(MyMagicExceptionTest.java:6)
        at MyMagicExceptionTest.main(MyMagicExceptionTest.java:14)

2. 斷言 (JDK 1.4)

JDK 1.4引入了一個新的關鍵字叫做"assert",它支持的就是斷言特性,Assertion就是用來檢測你關於程序邏輯的假設(例如:前提條件,後置條件和不變關系)。每個斷言包含一個boolean表達式,如果為true表示假設與執行結果相同,否則JVM就會拋出一個AssertionError。它表示你有一個錯誤的假設,它需要被修正,使用斷言比使用if-else表達式更好,它是對你假設的有效說明,並且它不影響程序性能。
assert聲明有兩種形式:

assert booleanExpr;
assert booleanExpr : errorMessageExpr;

當程序執行斷言的時候,首先會執行booleanExpr,如果結果為true,程序會正常執行,否則,就會拋出一個AssertionError,如果是上面第一種聲明,那麼AssertionError的創建使用的就是一個無參構造器,如果是上面第二種聲明,AssertionError的創建使用的就是一個有參構造器,參數為errorMessageExpr,如果errorMessageExpr是一個Object類型,它會自動執行toString函數轉為一個字符串傳遞給AssertionError的構造函數。

在檢測bug的時候,斷言是非常有用的,它可以充當程序流內部執行的說明,可以提高程序的可維護性。

斷言的一個比較好的使用地方就是在switch-case中,如果你相信肯定有一個case會被選擇執行,如果執行到了default-case是不合理的,那麼你可以在default-case中使用斷言。

 

 

public class AssertionSwitchTest {
   public static void main(String[] args) {
      char operator = '%';                  // assumed either '+', '-', '*', '/' only
      int operand1 = 5, operand2 = 6, result = 0;
      switch (operator) {
         case '+': result = operand1 + operand2; break;
         case '-': result = operand1 - operand2; break;
         case '*': result = operand1 * operand2; break;
         case '/': result = operand1 / operand2; break;
         default: assert false : "Unknown operator: " + operator;  // not plausible here
      }
      System.out.println(operand1 + " " + operator + " " + operand2 + " = " + result);
   }
}

在上面的例子中,"assert false"總是會觸發AssertionError。但是輸出結果是不同的,它取決於是否斷言可用,默認是不可用的。

 

 

> javac AssertionSwitchTest.java   // no option needed to compile
> java -ea AssertionSwitchTest     // enable assertion

Exception in thread "main" java.lang.AssertionError: %
        at AssertionSwitchTest.main(AssertionSwitchTest.java:11)

> java AssertionSwitchTest         // assertion disable by default

5 % 6 = 0

在上面的例子中,因為在default case中始終為flase,也就是總是會觸發AssertionError,所以你可以執行在default case裡面直接拋出一個AssertionError異常,這樣就不需要看斷言是否打開了。
default: throw new AssertionError("Unknown operator: " + operator);

另一種斷言的使用就是斷言一個內部變量的值。
public class AssertionTest {
   public static void main(String[] args) {
      int number = -5;    // assumed number is not negative
      // This assert also serve as documentation
      assert (number >= 0) : "number is negative: " + number;
      // do something
      System.out.println("The number is " + number);
   }
}

> java -ea AssertionSwitchTest     // enable assertion

Exception in thread "main" java.lang.AssertionError: -5
        at AssertionTest.main(AssertionTest.java:5)
斷言可以用來對程序進行驗證:
(1)內部變量的不變性:斷言一個值是否在某個限制內,例如:assert x > 0。
(2)類的不變性:斷言一個對象的狀態是否在某個限制內,類的不變性一般通過私有的boolean方法進行驗證,例如isValid() 方法用來驗證一個Circle對象的半徑值是否為正數。
(3)控制流的不變性:斷言程序是否執行到了某個特定的位置。
(4)方法的前置條件:主要檢查方法的參數或者它的對象的狀態。
(5)方法的後置條件:查看一個方法是否成功執行。


public方法的前置條件
對於傳入public方法的參數,不應該使用斷言來檢查參數的有效性,因為public方法是暴露的,任何人都可能使用非法參數調用這個方法,取而代之的是使用if-else語句進行判斷並且拋出IllegalArgumentException,另外,對於private方法,它的執行比較單一,所以斷言它的前置狀態是比較合適的。
// Constructor of Time class
public Time(int hour, int minute, int second) {
   if(hour < 0 || hour > 23 || minute < 0 || minute > 59 || second < 0 || second > 59) {
      throw new IllegalArgumentException();
   }
   this.hour = hour;
   this.minute = minute;
   this.second = second;
}

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved