java編程思想第12章筆記
1.概念
異常指的是在程序運(yùn)行過程中發(fā)生的異常事件,通常是由硬件問題或者程序設(shè)計問題所導(dǎo)致的。在Java等面向?qū)ο蟮木幊陶Z言中異常屬于對象。
早期的C語言的異常處理機(jī)制,通常是我們?nèi)藶榈膶Ψ祷亟Y(jié)果加一些標(biāo)志來進(jìn)行判定,比如發(fā)生錯誤返回什么標(biāo)志,正常情況下我們又是返回什么標(biāo)記,而這些都不是語言本身所賦予我們的,這種機(jī)制的問題在于,C語言的異常處理機(jī)制全是我們?nèi)藶榈亩x,這樣就會造成業(yè)務(wù)邏輯的主線受到異常處理的牽制,或者說是我們難免會將注意力轉(zhuǎn)移,并且造成業(yè)務(wù)邏輯與異常處理之間有很大程度上的纏繞。
2. 基本異常
異常清醒是指阻止當(dāng)前方法或者作用域繼續(xù)執(zhí)行的問題.
在java中異常同樣也是對象, 使用new可以在堆上創(chuàng)建異常對象
舉個異常最常見的例子好了, 我們都知道除數(shù)是不能為零的, , 所以做除法之前要做一個必要的檢查, 檢查除數(shù)不能為零, 否則將拋出異常
另一種情況就是當(dāng)使用一個對象的時候, 這個對象可能為null, 并沒有初始化, 遇到這樣的情況就需要拋出異常:
if (t == null)
?throw newNull PointerExcept();
2.1 異常參數(shù)
所有標(biāo)準(zhǔn)的異常類都有兩個構(gòu)造器, 一個是默認(rèn)構(gòu)造器, 另一個是字符串為參數(shù), 以便能把相關(guān)信息方法異常對象的構(gòu)造器
3 捕獲異常
3.1 try塊
如果在方法內(nèi)部拋出了異常, 那么這個方法將在拋出異常的過程中結(jié)束, 如果不想方法結(jié)束, 需要將可能產(chǎn)生異常的代碼放入try代碼塊中, 如果產(chǎn)生異常就會被try塊捕獲
3.2 異常處理程序
當(dāng)異常拋出后, 需要被處理, 處理需要在catch塊中進(jìn)行, 對于每個catch自居, 接收一個且僅接收一個特殊類型的的參數(shù)的方法, catch塊就是異常處理程序, 且異常處理程序必須緊跟try
4 創(chuàng)建自定義異常
自定義的異常類必須繼承異常類, 最簡單的就是繼承Exception類了, 來看一個簡單的示例:
class SimpleException extends Exception {}
public class InheritingExceptions {
public void f() throws SimpleException {
System.out.println("Throw SimpleException from f()");
throw new SimpleException();
}
public static void main(String[] args) {
InheritingExceptions sed = new InheritingExceptions();
try {
sed.f();
} catch (SimpleException e) {
System.out.println("Caught it");
}
}
}
Throw SimpleException from f()
Caught it
下面的例子將結(jié)果打印到了控制臺上了, 更好的做法是答應(yīng)道System.err, 因?yàn)镾ystem.out可能會被重定向, 同時這個例子也展示了一個接受字符串參數(shù)的構(gòu)造器:
class MyException extends Exception {
public MyException() {}
public MyException(String msg) {
super(msg);
}
}
public class FullConstructors {
public static void f() throws MyException {
System.out.println("Throwing MyException from f()");
throw new MyException();
}
public static void g() throws MyException {
System.out.println("Throwing MyException from g()");
throw new MyException("Originated in g()");
}
public static void main(String[] args) {
try {
f();
} catch (MyException e) {
e.printStackTrace(System.out);
}
try {
g();
} catch (MyException e) {
e.printStackTrace(System.out);
}
}
}
輸出結(jié)果:
Throwing MyException from f()
MyException
at FullConstructors.f(FullConstructors.java:11)
at FullConstructors.main(FullConstructors.java:20)
Throwing MyException from g()
MyException: Originated in g()
at FullConstructors.g(FullConstructors.java:15)
at FullConstructors.main(FullConstructors.java:25)
說一下這個printStackTrace()方法, 這個方法將打印"從方法調(diào)用出知道異常跑出處"的方法調(diào)用序列, 這里是將信息發(fā)送到了System.out, 無參數(shù)版本就是答應(yīng)道標(biāo)準(zhǔn)錯誤流
4 異常與記錄日志
使用java.util.Logging工具可以將輸出記錄到日記中, 基本的使用如下:
import java.util.logging.*;
import java.io.*;
class LoggingException extends Exception {
private static Logger logger = Logger.getLogger("LoggingException");
public LoggingException() {
StringWriter trace = new StringWriter();
printStackTrace(new PrintWriter(trace));
logger.severe(trace.toString());
}
}
public class LoggingExceptions {
public static void main(String[] args) {
try {
throw new LoggingException();
}catch (LoggingException e) {
System.err.println("Caught " + e);
}
try {
throw new LoggingException();
} catch (LoggingException e) {
System.err.println("Caught " + e);
}
}
}
輸出如下
十二月 01, 2016 9:14:31 下午 LoggingException <init>
嚴(yán)重: LoggingException
at LoggingExceptions.main(LoggingExceptions.java:16)
Caught LoggingException
十二月 01, 2016 9:14:31 下午 LoggingException <init>
嚴(yán)重: LoggingException
at LoggingExceptions.main(LoggingExceptions.java:23)
Caught LoggingException
靜態(tài)的Logger.getLogger()方法創(chuàng)建了一個String參數(shù)相關(guān)的Logger對象, 通常與錯誤相關(guān)的類名一致, 這個Logger對象會將其輸出發(fā)送到System.err.
severe()方法表示級別, 很明顯, severe表示嚴(yán)重的
但是更常見的情況是我們需要捕獲和記錄別人編寫的異常, 比如說這樣:
import java.util.logging.*;
import java.io.*;
public class LoggingException2 {
private static Logger logger = Logger.getLogger("LoggingException2");
static void logException(Exception e) {
StringWriter trace = new StringWriter();
e.printStackTrace(new PrintWriter(trace));
logger.severe(trace.toString());
}
public static void main(String[] args) {
try {
throw new NullPointerException();
} catch (NullPointerException e) {
logException(e);
}
}
}
輸出結(jié)果為:
十二月 01, 2016 9:24:49 下午 LoggingException2 logException
嚴(yán)重: java.lang.NullPointerException
at LoggingException2.main(LoggingException2.java:14)
當(dāng)然可以進(jìn)一步自定義異常, 比如加入額外的構(gòu)造器和成員, 得到更強(qiáng)大的功能.
5.異常說明
使用throws關(guān)鍵字, 緊跟在方法后面, 就像上面的程序例子一樣, 說明了這個方法可能會拋出的異常類型, 這就是異常說明
6. 捕獲所有的異常
可以只使用一個異常處理程序來捕獲所有類型的異常, 因?yàn)楫惓n愋偷幕惗际荅xception;
catch(Exception e) {
System.out.println("Caught an Exception");
}
6.1 棧軌跡
printStackTrace()方法所提供的信息可以通過getStackTrace()方法來直接訪問, 這個方法返回一個由棧軌跡中的元素所構(gòu)成的數(shù)組, 其中每一個元素表示棧中的一幀, 元素0是棧頂元素, 并且是調(diào)用序列中最后一個被調(diào)用的方法, 數(shù)組中的最后一個元素是調(diào)用序列中的第一個方法
下面是一個簡單的演示程序:
public class WhoCalled {
static void f() {
try {
throw new Exception();
} catch(Exception e) {
for(StackTraceElement ste : e.getStackTrace()) {
System.out.println(ste.getMethodName());
}
}
}
static void g() {
f();
}
static void h() {
g();
}
public static void main(String[] args) {
f();
System.out.println("--------------------------------------");
g();
System.out.println("--------------------------------------");
h();
}
}
輸出結(jié)果:
f
main
--------------------------------------
f
g
main
--------------------------------------
f
g
h
main
6.2 重新拋出異常
有時候需要將剛捕獲的異常重新拋出, 尤其是使用Exception捕獲所有異常的時候, 可以直接把它重新拋出, 重新拋出異常會把異常拋給上一級環(huán)境中的異常處理程序, 此外異常對象的所有信息都得以保存.
如果只是重新把當(dāng)前的異常對象拋出, 那么printStrackTrace()方法現(xiàn)實(shí)的將是原來異常拋出點(diǎn)的調(diào)用棧信息, 并非重新拋出點(diǎn)的信息.
public class Rethrowing {
public static void f() throws Exception {
System.out.println("Originating the exception in f()");
throw new Exception("thrown from f()");
}
public static void g() throws Exception {
try {
f();
} catch (Exception e) {
System.out.println("Inside g(), e.printStackTrace()");
e.printStackTrace(System.out);
throw e;
}
}
public static void h() throws Exception {
try {
g();
} catch (Exception e) {
System.out.println("inside h(), e.printStackTrace()");
e.printStackTrace(System.out);
// throw (Exception) e.fillInStackTrace();
throw e;
}
}
public static void main(String[] args) {
try {
g();
} catch (Exception e) {
System.out.println("main : printStackTrace()");
e.printStackTrace(System.out);
}
try {
h();
} catch (Exception e) {
System.out.println("main : printStackTrace()");
e.printStackTrace(System.out);
}
}
}
首先執(zhí)行這個結(jié)果
Originating the exception in f()
Inside g(), e.printStackTrace()
java.lang.Exception: thrown from f()
at Rethrowing.f(Rethrowing.java:4)
at Rethrowing.g(Rethrowing.java:9)
at Rethrowing.main(Rethrowing.java:29)
main : printStackTrace()
java.lang.Exception: thrown from f()
at Rethrowing.f(Rethrowing.java:4)
at Rethrowing.g(Rethrowing.java:9)
at Rethrowing.main(Rethrowing.java:29)
Originating the exception in f()
Inside g(), e.printStackTrace()
java.lang.Exception: thrown from f()
at Rethrowing.f(Rethrowing.java:4)
at Rethrowing.g(Rethrowing.java:9)
at Rethrowing.h(Rethrowing.java:19)
at Rethrowing.main(Rethrowing.java:36)
inside h(), e.printStackTrace()
java.lang.Exception: thrown from f()
at Rethrowing.f(Rethrowing.java:4)
at Rethrowing.g(Rethrowing.java:9)
at Rethrowing.h(Rethrowing.java:19)
at Rethrowing.main(Rethrowing.java:36)
main : printStackTrace()
java.lang.Exception: thrown from f()
at Rethrowing.f(Rethrowing.java:4)
at Rethrowing.g(Rethrowing.java:9)
at Rethrowing.h(Rethrowing.java:19)
at Rethrowing.main(Rethrowing.java:36)
去掉注釋并且將throw e;注釋掉, 結(jié)果是:
Originating the exception in f()
Inside g(), e.printStackTrace()
java.lang.Exception: thrown from f()
at Rethrowing.f(Rethrowing.java:4)
at Rethrowing.g(Rethrowing.java:9)
at Rethrowing.main(Rethrowing.java:29)
main : printStackTrace()
java.lang.Exception: thrown from f()
at Rethrowing.f(Rethrowing.java:4)
at Rethrowing.g(Rethrowing.java:9)
at Rethrowing.main(Rethrowing.java:29)
Originating the exception in f()
Inside g(), e.printStackTrace()
java.lang.Exception: thrown from f()
at Rethrowing.f(Rethrowing.java:4)
at Rethrowing.g(Rethrowing.java:9)
at Rethrowing.h(Rethrowing.java:19)
at Rethrowing.main(Rethrowing.java:36)
inside h(), e.printStackTrace()
java.lang.Exception: thrown from f()
at Rethrowing.f(Rethrowing.java:4)
at Rethrowing.g(Rethrowing.java:9)
at Rethrowing.h(Rethrowing.java:19)
at Rethrowing.main(Rethrowing.java:36)
main : printStackTrace()
java.lang.Exception: thrown from f()
at Rethrowing.h(Rethrowing.java:23)
at Rethrowing.main(Rethrowing.java:36)
對比第二個結(jié)果比第一個結(jié)果少了倒數(shù)三四行, fillInStackTrace方法會將棧軌跡清空并填充當(dāng)前棧軌跡
6.3 異常鏈
常常會想要在捕獲一個異常之后拋出另一個異常, 并想吧原始異常的信息保存下來, 這樣稱為異常鏈, throwable中initCause()方法可以接受一個cause對象作為參數(shù), 這個cause就是用來表示原始異常的, 這樣通過把原始異常傳遞給新的異常, 是的即使在當(dāng)前位置創(chuàng)建并拋出了新的異常, 也能通過這個異常鏈追蹤到最初發(fā)生異常的地方.
下面看一個可以動態(tài)的擴(kuò)展字段的dome:
class DynamicFieldsException extends Exception{}
public class DynamicFields {
private Object[][] fields;
public DynamicFields(int initialSize) {
fields = new Object[initialSize][2];
for (int i = 0;i < initialSize; i++) {
fields[i] = new Object[] {null, null};
}
}
public String toString() {
StringBuilder result = new StringBuilder();
for(Object[] obj : fields) {
result.append(obj[0]);
result.append(":");
result.append(obj[1]);
result.append("\n");
}
return result.toString();
}
// 判斷是否存在該字段, 存在返回下標(biāo), 如果不存在該字段返回-1
private int hasField(String id) {
for (int i = 0; i < fields.length; i++) {
if (id.equals(fields[i][0])) {
return i;
}
}
return -1;
}
// 返回該字段標(biāo)志符的下標(biāo), 如果不存在就拋出異常
private int getFieldNumber(String id) throws NoSuchFieldException {
int fieldNum = hasField(id);
if (fieldNum == -1) {
throw new NoSuchFieldException();
}
return fieldNum;
}
// 動態(tài)的擴(kuò)展
private int makeField(String id) {
for (int i = 0; i < fields.length; i++) {
if (fields[i][0] == null) {
fields[i][0] = id;
return i;
}
}
Object[][] temp = new Object[fields.length +1][2];
for (int i = 0; i < fields.length; i++) {
temp[i] = fields[i];
}
for (int i = fields.length; i < temp.length; i++) {
temp[i] = new Object[] {null, null};
}
fields = temp;
return makeField(id);
}
// 得到該字段標(biāo)識符的對應(yīng)的字段值
public Object getField(String id) throws NoSuchFieldException {
return fields[getFieldNumber(id)][1];
}
// 設(shè)置字段
public Object setField(String id, Object value) throws DynamicFieldsException {
// 字段值為空的話
if (value == null) {
DynamicFieldsException dfe = new DynamicFieldsException();
// 將此throwable的case初始化為指定值
dfe.initCause(new NullPointerException());
throw dfe;
}
int fieldNumber = hasField(id);
// 如果該字段對應(yīng)字段標(biāo)識符不存在的話
if (fieldNumber == -1) {
fieldNumber = makeField(id);
}
Object result = null;
try {
result = getField(id);
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
fields[fieldNumber][1] = value;
return result;
}
public static void main(String[] args) {
DynamicFields df = new DynamicFields(3);
System.out.println(df);
try {
df.setField("d", "A value for d");
df.setField("number", 47);
df.setField("number2", 48);
System.out.println(df);
df.setField("d", "A new value for d");
df.setField("number3", 11);
System.out.println(df);
System.out.println("df.getField(\"d\")" + df.getField("d"));
Object field = df.setField("d", null); // 產(chǎn)生異常
} catch (NoSuchFieldException e) {
e.printStackTrace(System.out);
} catch (DynamicFieldsException e) {
e.printStackTrace(System.out);
}
}
}
運(yùn)行后的輸出結(jié)果為:
null:null
null:null
null:null
d:A value for d
number:47
number2:48
d:A new value for d
number:47
number2:48
number3:11
df.getField("d")A new value for d
DynamicFieldsException
at DynamicFields.setField(DynamicFields.java:64)
at DynamicFields.main(DynamicFields.java:94)
Caused by: java.lang.NullPointerException
at DynamicFields.setField(DynamicFields.java:65)
... 1 more