問:java 異常有哪幾種缠犀,特點(diǎn)是什么数苫?
答:異常是發(fā)生在程序執(zhí)行過程中阻礙程序正常執(zhí)行的錯(cuò)誤操作,只要在 Java 語句執(zhí)行中產(chǎn)生異常夭坪,則一個(gè)異常對象就會(huì)被創(chuàng)建文判。
Throwable 是所有異常的父類过椎,它有兩個(gè)直接子類 Error 和 Exception室梅,其中 Exception 又被繼續(xù)劃分為被檢查的異常(checked exception)和運(yùn)行時(shí)的異常(runtime exception,即不受檢查的異常)疚宇。
Error 表示系統(tǒng)錯(cuò)誤亡鼠,通常不能預(yù)期和恢復(fù)(譬如 JVM 崩潰、內(nèi)存不足等)敷待;
被檢查的異常(Checked exception)在程序中能預(yù)期且要嘗試修復(fù)(如我們必須捕獲 FileNotFoundException 異常并為用戶提供有用信息和合適日志來進(jìn)行調(diào)試间涵,Exception 是所有被檢查的異常的父類);
運(yùn)行時(shí)異常(Runtime Exception)又稱為不受檢查異常榜揖,譬如我們檢索數(shù)組元素之前必須確認(rèn)數(shù)組的長度勾哩,否則就可能會(huì)拋出ArrayIndexOutOfBoundException 運(yùn)行時(shí)異常抗蠢,RuntimeException 是所有運(yùn)行時(shí)異常的父類。
問:java 中 throw 與 throws 的區(qū)別是什么思劳?
答:throw 使用的位置在方法中迅矛,后面跟的異常對象實(shí)例,表示拋出異常潜叛,由方法體內(nèi)語句處理秽褒,如果方法中有 throw 拋出 RuntimeException 及其子類,則聲明上可以沒有 throws威兜;如果方法中有 throw 拋出 Exception 及其子類销斟,則聲明上必須有 throws。
public static void main(String[] args) {
String s = "abc";
if(s.equals("abc")) {
throw new NumberFormatException();
} else {
System.out.println(s);
}
}
throws 使用的位置在方法參數(shù)小括號(hào)后面椒舵,后面跟的是一個(gè)或者多個(gè)異常類名且用逗號(hào)隔開蚂踊,表示拋出異常并交給調(diào)用者去處理,如果后面根據(jù)的是 RuntimeException 及其子類則該方法可以不用處理笔宿,如果后面根據(jù)的是 Exception 及其子類則必須要編寫代碼進(jìn)行處理或者調(diào)用的時(shí)候拋出悴势。
public class testThrows(){
public static void function() throws NumberFormatException{
String s = "abc";
System.out.println(Double.parseDouble(s));
}
public static void main(String[] args) {
try {
function();
} catch (NumberFormatException e) {
System.err.println("非數(shù)據(jù)類型不能強(qiáng)制類型轉(zhuǎn)換。");
}
}
問:java 中被檢查的異常和不受檢查的異常有什么區(qū)別措伐?
答:
被檢查的異常應(yīng)該用 try - catch 塊代碼處理或用 throws 關(guān)鍵字拋出特纤;
不受檢查的異常在程序中不要求被處理或用 throws 拋出;
Exception 是所有被檢查異常的基類侥加,而 RuntimeException(是 Exception 的子類) 是所有不受檢查異常的基類捧存;被檢查的異常適用于那些不是因程序引起的錯(cuò)誤情況(如 FileNotFoundException),而不被檢查的異常通常都是由于糟糕的編程引起(如 NullPointerException)担败。
問:java 中 Error 和 Exception 有什么區(qū)別昔穴?
答:
Error 表示系統(tǒng)級(jí)的錯(cuò)誤,是 java 運(yùn)行環(huán)境內(nèi)部錯(cuò)誤或者硬件問題提前,不能指望程序來處理這樣的問題吗货,除了退出運(yùn)行外別無選擇,它是 java 虛擬機(jī)拋出的狈网。
Exception 表示程序需要捕捉宙搬、需要處理的異常,是由與程序設(shè)計(jì)的不完善而出現(xiàn)的問題拓哺,程序可以處理的問題勇垛。
問:java 中什么是異常鏈?
答:異常鏈?zhǔn)侵冈谶M(jìn)行一個(gè)異常處理時(shí)拋出了另外一個(gè)異常士鸥,由此產(chǎn)生了一個(gè)異常鏈條闲孤,大多用于將受檢查異常(checked exception)封裝成為非受檢查異常(unchecked exception)或者 RuntimeException。
特別注意如果你因?yàn)橐粋€(gè)異常而決定拋出另一個(gè)新的異常時(shí)一定要包含原有的異常烤礁,這樣處理程序才可以通過 getCause() 和 initCause() 方法來訪問異常最終的根源讼积。
問:java 中如何編寫自定義異常肥照?
答:可以通過繼承 Exception 類或其任何子類來實(shí)現(xiàn)自己的自定義異常類,自定義異常類可以有自己的變量和方法來傳遞錯(cuò)誤代碼或其它異常相關(guān)信息來處理異常勤众。下面是一個(gè)自定義異常的常見模板:
public class DemoException extends IOException {
private static final long serialVersionUID = 123456789L;
private String errorCode = "DemoException";
public DemoException(String msg, String errorCode) {
super(msg);
this.errorCode = errorCode;
}
public String getErrorCode() {
return this.errorCode;
}
}
問:請簡單描述下面方法的執(zhí)行流程和最終返回值是多少建峭?
public static int test1() {
int ret = 0;
try {
return ret;
} finally {
ret = 2;
}
}
public static int test2() {
int ret = 0;
try {
int a = 5 / 0;
return ret;
} finally {
return 2;
}
}
public static void test3() {
try {
int a = 5 / 0;
} finally {
throw new RuntimeException("hello");
}
}
答:本題旨在考察 try-catch-finally(可以只有 try 和 finally) 塊的用法踩坑經(jīng)驗(yàn),具體解析如下决摧。
test1 方法運(yùn)行返回 0亿蒸,因?yàn)閳?zhí)行到 try 的 return ret; 語句前會(huì)先將返回值 ret 保存在一個(gè)臨時(shí)變量中,然后才執(zhí)行 finally 語句掌桩,最后 try 再返回那個(gè)臨時(shí)變量边锁,finally 中對 ret 的修改不會(huì)被返回。
test2 方法運(yùn)行返回 2波岛,因?yàn)?5/0 會(huì)觸發(fā) ArithmeticException 異常茅坛,但是 finally 中有 return 語句蛇数,finally 中 return 不僅會(huì)覆蓋 try 和 catch 內(nèi)的返回值且還會(huì)掩蓋 try 和 catch 內(nèi)的異常荠瘪,就像異常沒有發(fā)生一樣(特別注意,當(dāng) finally 中沒有 return 時(shí)該方法運(yùn)行會(huì)拋出 ArithmeticException 異常)蘑志,所以這個(gè)方法就會(huì)返回 2煌茬,而且不再向上傳遞異常了斥铺。
test3 方法運(yùn)行拋出 hello 異常,因?yàn)槿绻?finally 中拋出了異常坛善,則原異常就會(huì)被掩蓋晾蜘。
因此為避免代碼邏輯混淆,我們應(yīng)該避免在 finally 中使用 return 語句或者拋出異常眠屎,如果調(diào)用的其他代碼可能拋出異常剔交,則應(yīng)該捕獲異常并進(jìn)行處理。
問:如果執(zhí)行 finally 代碼塊之前方法返回了結(jié)果或者 JVM 退出了改衩,這時(shí) finally 塊中的代碼還會(huì)執(zhí)行嗎岖常?
答:只有在 try 里面通過 System.exit(0) 來退出 JVM 的情況下 finally 塊中的代碼才不會(huì)執(zhí)行,其他 return 等情況都會(huì)調(diào)用葫督,所以在不終止 JVM 的情況下 finally 中的代碼一定會(huì)執(zhí)行竭鞍。
問:分別說說下面代碼片段都有什么問題?
public static void func() throws RuntimeException, NullPointerException {
throw new RuntimeException("func exception");
}
public static void main(String args[]) {
try {
func();
} catch (Exception ex) {
ex.printStackTrace();
} catch (RuntimeException re) {
re.printStackTrace();
}
}
上面代碼段對于 func 方法后面 throws 列出的異常類型是不分先后順序的候衍,所以 func 方法是沒問題的笼蛛;對于 main 方法中在捕獲 RuntimeException 類型變量 re 的地方會(huì)編譯錯(cuò)誤洒放,因?yàn)?Exception 是 RuntimeException 的超類蛉鹿,func 方法執(zhí)行的異常都會(huì)被第一個(gè) catch 塊捕獲,所以會(huì)報(bào)編譯時(shí)錯(cuò)誤往湿。
public class Base {
public void func() throws IOException {
throw new IOException("Base IOException");
}
}
public class Sub extends Base {
public void func() throws Exception {
throw new Exception("Sub Exception");
}
}
如上代碼片段在編譯時(shí)子類 func 方法會(huì)出現(xiàn)編譯異常妖异,因?yàn)樵?java 中重寫方法拋出的異常不能是原方法拋出異常的父類惋戏,這里 func 方法在父類中拋出了 IOException,所有在子類中的 func 方法只能拋出 IOExcepition 或是其子類他膳,但不能是其父類响逢。
public static void func() {
}
public static void main(String args[]) {
try {
func();
} catch (IOException e) {
e.printStackTrace();
}
}
上面代碼段編譯時(shí)在 IOException 時(shí)會(huì)出現(xiàn)編譯錯(cuò)誤,因?yàn)?IOException 是受檢查異常棕孙,而 func 方法并沒有拋出 IOException舔亭,所以編譯報(bào)錯(cuò),但是如果將 IOException 改為 Exception(或者 NullPointerException 等)則編譯報(bào)錯(cuò)將消失蟀俊,因?yàn)?Exception 可以用來捕捉所有運(yùn)行時(shí)異常钦铺,這樣就不需要聲明拋出語句。
答:通過上面幾個(gè)代碼片段可以看出我們在書寫多 catch 塊時(shí)要保證異常類型的優(yōu)先級(jí)書寫順序肢预,要保證子類靠前父類靠后的原則矛洞;此外在 java 中重寫方法拋出的異常不能是原方法拋出異常的父類;如果方法沒有拋出受檢查類型異常則在調(diào)用方法的地方就不能主動(dòng)添加受檢查類型異常捕獲烫映,但是可以添加運(yùn)行時(shí)異痴颖荆或者 Exception 捕獲。
問:關(guān)于 java 中的異常處理你有什么心得或者經(jīng)驗(yàn)锭沟?
答:這其實(shí)是一個(gè)經(jīng)驗(yàn)題抽兆,答案不局限的,可以自由發(fā)揮族淮,下面給出幾個(gè)示例點(diǎn)郊丛。
方法返回值盡量不要使用 null(特殊場景除外),這樣可以避免很多 NullPointerException 異常瞧筛。
catch 住了如果真的沒必要處理則至少加行打印厉熟,這樣可在將來方便排查問題。
接口方法拋出的異常盡量保證是運(yùn)行時(shí)異常類型较幌,除非迫不得已才拋出檢查類型異常揍瑟。
避免在 finally 中使用 return 語句或者拋出異常,如果調(diào)用的其他代碼可能拋出異常則應(yīng)該捕獲異常并進(jìn)行處理乍炉,因?yàn)?finally 中 return 不僅會(huì)覆蓋 try 和 catch 內(nèi)的返回值且還會(huì)掩蓋 try 和 catch 內(nèi)的異常绢片,就像異常沒有發(fā)生一樣(特別注意,當(dāng) try-finally 中沒有 return 時(shí)該方法運(yùn)行會(huì)繼續(xù)拋出異常)岛琼。
盡量不要在 catch 塊中壓制異常(即什么也不處理直接 return)底循,因?yàn)檫@樣以后無論拋出什么異常都會(huì)被忽略,以至沒有留下任何問題線索槐瑞,如果在這一層不知道如何處理異常最好將異常重新拋出由上層決定如何處理異常熙涤。
方法定義中 throws 后面盡量定義具體的異常列表,不要直接 throws Exception。
捕獲異常時(shí)盡量捕獲具體的異常類型而不要直接捕獲其父類祠挫,這樣不容易造成混亂那槽。
避免在 finally 塊中拋出異常,不然第一個(gè)異常的調(diào)用棧會(huì)丟失等舔。
不要使用異成Ь模控制程序的流程,譬如本應(yīng)該使用 if 語句進(jìn)行條件判斷的情況下卻使用異常處理是非常不好的習(xí)慣慌植,會(huì)嚴(yán)重影響性能甚牲。
不要直接捕獲 Throwable 類,因?yàn)?Error 是 Throwable 類的子類蝶柿,當(dāng)應(yīng)用拋出 Errors 的時(shí)候一般都是不可恢復(fù)的情況鳖藕。
問:java 中 finally 塊一定會(huì)執(zhí)行嗎?
答:不一定只锭,分情況著恩。
因?yàn)槭紫认胍獔?zhí)行 finally 塊的前提是必須執(zhí)行到了 try 塊,當(dāng)在 try 塊或者 catch 塊中有 System.exit(0); 這樣的語句存在時(shí) finally 塊就不會(huì)被執(zhí)行到了蜻展,因?yàn)槌绦虮唤Y(jié)束了喉誊。
此外當(dāng)在 try 塊或者 catch 塊里 return 時(shí) finally 會(huì)被執(zhí)行;而且 finally 塊里 return 語句會(huì)把 try 塊或者 catch 塊里的 return 語句效果給覆蓋掉且吞掉了異常纵顾。
問:java 中什么時(shí)候使用斷言(assert)伍茄?
答:斷言在開發(fā)中是一種常用的調(diào)試方式,很多開發(fā)語言中都支持這種機(jī)制施逾。一般來說敷矫,斷言用于保證程序最基本、關(guān)鍵的正確性汉额,斷言檢查通常在開發(fā)和測試時(shí)開啟曹仗,為了保證程序的執(zhí)行效率,在軟件發(fā)布后斷言檢查通常是關(guān)閉的蠕搜,斷言是一個(gè)包含布爾表達(dá)式的語句怎茫,在執(zhí)行這個(gè)語句時(shí)假定該表達(dá)式為 true,如果表達(dá)式的值為 false 則系統(tǒng)會(huì)報(bào)告一個(gè) AssertionError妓灌。