一起聊聊Java中的自定義異常
在學習Java的過程中,想必大傢都一定學習過異常這個篇章,異常的基本特性和使用這裡就不再多講瞭。想必大傢都能夠理解看懂,並正確使用。
但是,光學會基本異常處理和使用不夠的,在工作中常會有自定義業務異常的場景,根據不同的業務異常做對應異常處理,出現異常並不可怕,有時候是需要使用異常來驅動業務的處理,例如: 在使用唯一約束的數據庫的時候,如果插入一條重復的數據,那麼可以通過捕獲唯一約束異常DuplicateKeyException,如果出現CommunicationsException是不是又要去處理呢?如果兩種情況都使用同樣業務邏輯來處理,是不是同樣捕獲呢?這個時候,其實如果在DAO層統一捕獲Exception,然後向上拋出自定義異常,在調用成層根據對應的業務異常再進行處理,而且自定義異常能做很多輕量化處理(請看下文解釋),是不是方便很多呢?所以這裡自定義業務異常:既是對業務不同異常場景下的區分,又是通過異常來驅動業務流程的處理,以自定義異常好處很多。
Java中的異常
Java中默認的異常信息有哪些呢?Java程序中捕獲異常之後會將異常進行輸出,不知道細心的同學有沒有註意到一點,輸出的異常是什麼東西呢?下面來看一個常見的ArithmeticException異常:
java.lang.ArithmeticException: / by zero at greenhouse.ExceptionTest.testException(ExceptionTest.java:16) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:76) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184) at org.junit.runners.ParentRunner.run(ParentRunner.java:236) at org.junit.runner.JUnitCore.run(JUnitCore.java:157) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68) at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47) at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242) at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
再看看一個Java程序員耳熟能詳的NullPointerException空指針異常:
java.lang.NullPointerException at greenhouse.ExceptionTest.testException(ExceptionTest.java:16) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:76) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184) at org.junit.runners.ParentRunner.run(ParentRunner.java:236) at org.junit.runner.JUnitCore.run(JUnitCore.java:157) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68) at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47) at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242) at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
大傢有沒有發現一個特點,就是異常的輸出中能夠精確的輸出異常出現的地點,精確到每一行代碼,還有後面一大堆的執行過程類調用,也都打印出來瞭,這些信息從哪兒來呢?
這些信息是從棧中獲取的,在打印異常日志的時候,會從JVM 棧中去獲取這些調用信息。能夠精確的定位異常出現的異常當然是好,但是我們有時候考慮到程序的性能,以及一些需求時,我們有時候並不需要完全的打印這些信息,並且去方法調用棧中獲取相應的信息,是有性能消耗的,對於一些性能要求高的程序,我們完全可以在異常處理方面為程序性能做一個性能提升。
自定義Java異常類
所以如何避免輸出這些堆棧信息呢? 那麼自定義異常就可以解決這個問題:
首先,自定義異常需要繼承RuntimeException,然後,再通過是重寫fillInStackTrace,toString 方法,例如下面我定義一個AppException異常:
package com.green.monitor.common.exception; import java.text.MessageFormat; /** * 自定義異常類 */ public class AppException extends RuntimeException { private boolean isSuccess = false; private String key; private String info; public AppException(String key) { super(key); this.key = key; this.info = key; } public AppException(String key, String message) { super(MessageFormat.format("{0}[{1}]", key, message)); this.key = key; this.info = message; } public AppException(String message, String key, String info) { super(message); this.key = key; this.info = info; } public boolean isSuccess() { return isSuccess; } public String getKey() { return key; } public void setKey(String key) { this.key = key; } public String getInfo() { return info; } public void setInfo(String info) { this.info = info; } @Override public Throwable fillInStackTrace() { return this; } @Override public String toString() { return MessageFormat.format("{0}[{1}]",this.key,this.info); } }
Java異常源碼
那麼為什麼要重寫fillInStackTrace,和 toString 方法呢? 我們首先來看源碼是怎麼一回事。
public class RuntimeException extends Exception { static final long serialVersionUID = -7034897190745766939L; /** Constructs a new runtime exception with <code>null</code> as its * detail message. The cause is not initialized, and may subsequently be * initialized by a call to {@link #initCause}. */ public RuntimeException() { super(); } /** Constructs a new runtime exception with the specified detail message. * The cause is not initialized, and may subsequently be initialized by a * call to {@link #initCause}. * * @param message the detail message. The detail message is saved for * later retrieval by the {@link #getMessage()} method. */ public RuntimeException(String message) { super(message); } /** * Constructs a new runtime exception with the specified detail message and * cause. <p>Note that the detail message associated with * <code>cause</code> is <i>not</i> automatically incorporated in * this runtime exception's detail message. * * @param message the detail message (which is saved for later retrieval * by the {@link #getMessage()} method). * @param cause the cause (which is saved for later retrieval by the * {@link #getCause()} method). (A <tt>null</tt> value is * permitted, and indicates that the cause is nonexistent or * unknown.) * @since 1.4 */ public RuntimeException(String message, Throwable cause) { super(message, cause); } /** Constructs a new runtime exception with the specified cause and a * detail message of <tt>(cause==null ? null : cause.toString())</tt> * (which typically contains the class and detail message of * <tt>cause</tt>). This constructor is useful for runtime exceptions * that are little more than wrappers for other throwables. * * @param cause the cause (which is saved for later retrieval by the * {@link #getCause()} method). (A <tt>null</tt> value is * permitted, and indicates that the cause is nonexistent or * unknown.) * @since 1.4 */ public RuntimeException(Throwable cause) { super(cause); } }
RuntimeException是繼承Exception,但是它裡面隻是調用瞭父類的方法,本身是沒有做什麼其餘的操作。那麼繼續看Exception裡面是怎麼回事。
public class Exception extends Throwable { static final long serialVersionUID = -3387516993124229948L; /** * Constructs a new exception with <code>null</code> as its detail message. * The cause is not initialized, and may subsequently be initialized by a * call to {@link #initCause}. */ public Exception() { super(); } /** * Constructs a new exception with the specified detail message. The * cause is not initialized, and may subsequently be initialized by * a call to {@link #initCause}. * * @param message the detail message. The detail message is saved for * later retrieval by the {@link #getMessage()} method. */ public Exception(String message) { super(message); } /** * Constructs a new exception with the specified detail message and * cause. <p>Note that the detail message associated with * <code>cause</code> is <i>not</i> automatically incorporated in * this exception's detail message. * * @param message the detail message (which is saved for later retrieval * by the {@link #getMessage()} method). * @param cause the cause (which is saved for later retrieval by the * {@link #getCause()} method). (A <tt>null</tt> value is * permitted, and indicates that the cause is nonexistent or * unknown.) * @since 1.4 */ public Exception(String message, Throwable cause) { super(message, cause); } /** * Constructs a new exception with the specified cause and a detail * message of <tt>(cause==null ? null : cause.toString())</tt> (which * typically contains the class and detail message of <tt>cause</tt>). * This constructor is useful for exceptions that are little more than * wrappers for other throwables (for example, {@link * java.security.PrivilegedActionException}). * * @param cause the cause (which is saved for later retrieval by the * {@link #getCause()} method). (A <tt>null</tt> value is * permitted, and indicates that the cause is nonexistent or * unknown.) * @since 1.4 */ public Exception(Throwable cause) { super(cause); } }
從源碼中可以看到,Exception裡面也是直接調用瞭父類的方法,和RuntimeException一樣,自己其實並沒有做什麼。那麼直接來看Throwable裡面是怎麼一回事:
public class Throwable implements Serializable { public Throwable(String message) { fillInStackTrace(); detailMessage = message; } /** * Fills in the execution stack trace. This method records within this * <code>Throwable</code> object information about the current state of * the stack frames for the current thread. * * @return a reference to this <code>Throwable</code> instance. * @see java.lang.Throwable#printStackTrace() */ public synchronized native Throwable fillInStackTrace(); /** * Provides programmatic access to the stack trace information printed by * {@link #printStackTrace()}. Returns an array of stack trace elements, * each representing one stack frame. The zeroth element of the array * (assuming the array's length is non-zero) represents the top of the * stack, which is the last method invocation in the sequence. Typically, * this is the point at which this throwable was created and thrown. * The last element of the array (assuming the array's length is non-zero) * represents the bottom of the stack, which is the first method invocation * in the sequence. * * <p>Some virtual machines may, under some circumstances, omit one * or more stack frames from the stack trace. In the extreme case, * a virtual machine that has no stack trace information concerning * this throwable is permitted to return a zero-length array from this * method. Generally speaking, the array returned by this method will * contain one element for every frame that would be printed by * <tt>printStackTrace</tt>. * * @return an array of stack trace elements representing the stack trace * pertaining to this throwable. * @since 1.4 */ public StackTraceElement[] getStackTrace() { return (StackTraceElement[]) getOurStackTrace().clone(); } private synchronized StackTraceElement[] getOurStackTrace() { // Initialize stack trace if this is the first call to this method if (stackTrace == null) { int depth = getStackTraceDepth(); stackTrace = new StackTraceElement[depth]; for (int i=0; i < depth; i++) stackTrace[i] = getStackTraceElement(i); } return stackTrace; } /** * Returns the number of elements in the stack trace (or 0 if the stack * trace is unavailable). * * package-protection for use by SharedSecrets. */ native int getStackTraceDepth(); /** * Returns the specified element of the stack trace. * * package-protection for use by SharedSecrets. * * @param index index of the element to return. * @throws IndexOutOfBoundsException if <tt>index < 0 || * index >= getStackTraceDepth() </tt> */ native StackTraceElement getStackTraceElement(int index); /** * Returns a short description of this throwable. * The result is the concatenation of: * <ul> * <li> the {@linkplain Class#getName() name} of the class of this object * <li> ": " (a colon and a space) * <li> the result of invoking this object's {@link #getLocalizedMessage} * method * </ul> * If <tt>getLocalizedMessage</tt> returns <tt>null</tt>, then just * the class name is returned. * * @return a string representation of this throwable. */ public String toString() { String s = getClass().getName(); String message = getLocalizedMessage(); return (message != null) ? (s + ": " + message) : s; }
從源碼中可以看到,到Throwable就幾乎到頭瞭,在fillInStackTrace() 方法是一個native方法,這方法也就是會調用底層的C語言,返回一個Throwable對象,toString 方法,返回的是throwable的簡短描述信息,並且在getStackTrace 方法和 getOurStackTrace 中調用的都是native方法getStackTraceElement,而這個方法是返回指定的棧元素信息,所以這個過程肯定是消耗性能的,那麼我們自定義異常中的重寫toString方法和fillInStackTrace方法就可以不從棧中去獲取異常信息,直接輸出,這樣對系統和程序來說,相對就沒有那麼"重",是一個優化性能的非常好的辦法。
按照上面我們舉例的自定義AppException異常,如果出現異常瞭,這個AppException異常輸出是什麼樣的信息呢?請看下面吧:
@Test public void testException(){ try { String str =null; System.out.println(str.charAt(0)); }catch (Exception e){ throw new AppException("000001","空指針異常"); } }
執行上面單元測試,在異常異常的時候,系統將會打印我們自定義的異常信息:
000001[空指針異常]
Process finished with exit code -1
所以特別簡潔,優化瞭系統程序性能,讓程序不這麼“重”,所以對於性能要求特別要求的系統,趕緊自定義業務異常試一試吧!
到此這篇關於一起聊聊Java中的自定義異常的文章就介紹到這瞭,更多相關Java自定義異常內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- Assert.assertEquals的使用方法及註意事項說明
- 解決json串和實體類字段不一致的問題
- 解決使用stream將list轉map時,key重復導致報錯的問題
- java實現統一異常處理的示例
- Java自定義異常與異常使用的最佳方式