溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

一文帶你快速讀懂Java中的異常

發(fā)布時(shí)間:2020-11-21 16:08:39 來源:億速云 閱讀:120 作者:Leah 欄目:編程語(yǔ)言

這篇文章給大家介紹一文帶你快速讀懂Java中的異常,內(nèi)容非常詳細(xì),感興趣的小伙伴們可以參考借鑒,希望對(duì)大家能有所幫助。

什么是異常?

異常是Java語(yǔ)言中的一部分,它代表程序中由各種原因引起的“不正常”因素。 那么在程序中什么樣的情況才算不正常呢? 我認(rèn)為可以這樣定義:如果出現(xiàn)了這么一種情況,它打斷了程序期望的執(zhí)行流程,改變了控制流的方向(包括讓JVM停掉),那么就可以認(rèn)為發(fā)生了不正常情況,也就是引發(fā)了異常。舉個(gè)例子顯而易見的例子:

FileOutputStream out = null; 
try { 
  out = new FileOutputStream("abc.text"); 
  out.write(1); 
  System.out.println("寫入成功"); 
} catch (FileNotFoundException e) { 
   
  System.out.println("要寫入的文件不存在"); 
  e.printStackTrace(); 
   
} catch (IOException e) { 
   
  System.out.println("發(fā)生了IO錯(cuò)誤"); 
  e.printStackTrace(); 
   
}finally{ 
  if(out != null){ 
    try { 
      out.close(); 
    } catch (IOException e) { 
      e.printStackTrace(); 
    } 
  } 
} 

我調(diào)用FileOutputStream.write(int)方法期望向一個(gè)文件寫入一個(gè)字節(jié)的數(shù)據(jù),如果在寫入時(shí)發(fā)生了IO錯(cuò)誤, 那么就發(fā)生了“不正常情況”,也就是拋出IOException,進(jìn)而程序的控制流發(fā)生了改變,本來如果寫入成功的話, 會(huì)執(zhí)行FileOutputStream.write(int)下一句代碼, 現(xiàn)在發(fā)生了異常, 那么程序要跳到IOException對(duì)應(yīng)的catch塊中,去處理這個(gè)異常情況。

異常體系和分類

Java以面向?qū)ο蟮姆绞絹砉芾懋惓G闆r,也就是說,Java程序執(zhí)行時(shí)遇到的各種問題都被封裝成了對(duì)象,并且這些對(duì)象之間具有繼承關(guān)系。java中的讓人不爽的“不正常情況”可以分為兩種,一種叫做Error,一種是在程序中到處可見的Exception,而他們都繼承自Throwable。Exception又分為編譯時(shí)受檢查異常(Checked Exception)和運(yùn)行時(shí)異常(RuntimeException)。如下圖所示(該圖片來源于網(wǎng)絡(luò)):

一般情況下,Error代表虛擬機(jī)在執(zhí)行程序時(shí)遇到嚴(yán)重問題,不能再回復(fù)執(zhí)行了,這屬于重大事故,虛擬機(jī)要掛掉的,一句話概括就是“這病沒得治,等死就行了”。那么打開JDK的文檔,列舉幾種Error:

VirtualMachineError: 當(dāng) Java 虛擬機(jī)崩潰或用盡了它繼續(xù)操作所需的資源時(shí),拋出該錯(cuò)誤。

ClassFormatError:當(dāng) Java 虛擬機(jī)試圖讀取類文件并確定該文件存在格式錯(cuò)誤或無法解釋為類文件時(shí),拋出該錯(cuò)誤。

NoClassDefFoundError:當(dāng) Java 虛擬機(jī)或 ClassLoader 實(shí)例試圖在類的定義中加載,但無法找到該類的定義時(shí),拋出此異常。

而相對(duì)于Error,Exception是java程序中遇到的“不那么嚴(yán)重”的問題,這種問題是可以處理的,當(dāng)處理了這個(gè)問題后,程序還可以繼續(xù)執(zhí)行。一句話概括,“這是病,得治,這病是可以治好的”。

Exception就比較常見了,隨便舉幾個(gè)例子。當(dāng)創(chuàng)建文件輸入流時(shí), 發(fā)現(xiàn)文件不存在,那么拋出FileNotFoundException,但是異??梢蕴幚恚瑳]法讀文件,并不會(huì)在很大長(zhǎng)度上影響整個(gè)程序的執(zhí)行,畢竟不能讀文件,程序還可以執(zhí)行其他邏輯。下面舉一個(gè)趣味性的示例:

public class Travel { 
  private static int power = 100; 
  private static boolean bridgeIsOk = true; 
  public static void main(String[] args) { 
    //描述一下坐火車旅游的過程 
    System.out.println("從濟(jì)南出發(fā), 到北京旅游"); 
    System.out.println("列車開到德州"); 
    //中途給媽媽打個(gè)電話 
    try{ 
      telToMom(); 
    }catch(BatteryDiedException e){ 
      System.out.println("換一塊電池, 繼續(xù)旅程"); 
    } 
    //橋斷了 
    if(!bridgeIsOk){ 
      System.out.println("旅程結(jié)束"); 
      throw new BridgeBreakError("橋斷了,列車停止運(yùn)行"); 
    } 
    System.out.println("到北京站,下車"); 
    //下雨了 
    try{ 
      throw new RainException("下雨了"); 
    }catch(RainException e){ 
      System.out.println("撐起準(zhǔn)備的雨傘, 繼續(xù)旅程"); 
    } 
  } 
  private static void telToMom() throws BatteryDiedException{ 
    if(power == 0){ //手機(jī)電量為0 
      System.out.println("手機(jī)沒電了"); 
      throw new BatteryDiedException("手機(jī)沒電了"); 
    } 
    System.out.println("給媽媽打電話"); 
  } 
  static class BatteryDiedException extends Exception{ 
    public BatteryDiedException(String msg){ 
      super(msg); 
    } 
  } 
  static class BridgeBreakError extends Error{ 
    public BridgeBreakError(String msg){ 
      super(msg); 
    } 
  } 
  static class RainException extends Exception{ 
    public RainException(String msg){ 
      super(msg); 
    } 
  } 
} 

上面的代碼描述了一次旅行, 如果在旅途中給媽媽打電話,發(fā)現(xiàn)手機(jī)沒電了, 拋出BatteryDiedException,但是這種異常是可以應(yīng)付的,直接換一塊準(zhǔn)備的備用電池就OK了,下了車之后,天下雨了,拋出RainException,這種異常也可以應(yīng)付,因?yàn)樘崆皽?zhǔn)備了雨傘。這兩種情況都是可以恢復(fù)的,遇到之后,只需做一定的處理,旅程還能繼續(xù)。如果在途中遇到橋斷裂的情況,那么列車必須停止運(yùn)行,這次旅行就泡湯了,也就是說已經(jīng)不能從這種惡劣情況中恢復(fù)過來,所以直接拋出BridgeBreakError。

編譯時(shí)受檢查異常和運(yùn)行時(shí)異常

那么再說一下編譯時(shí)受檢查異常和運(yùn)行時(shí)異常?;仡櫼幌庐惓5亩x:程序在執(zhí)行時(shí)遇到的不正常情況。那么既然是運(yùn)行時(shí)遇到的問題,怎么還有一個(gè)編譯時(shí)受檢查異常呢?其實(shí)編譯時(shí)根本不會(huì)發(fā)生異常,只會(huì)在語(yǔ)法錯(cuò)誤的情況下編譯失敗,但是這和異常是不相關(guān)的概念。異常只是運(yùn)行時(shí)的行為。那么編譯時(shí)受檢查異常又是一個(gè)什么概念呢?要理解受檢查異常存在的意義,那么必須明確編碼者所處的位置,也可以說編碼者的角色, 即:我是功能的具體實(shí)現(xiàn)者, 還是功能的使用者,也可以說,我是方法的編寫者還是已有方法的調(diào)用者。如果我是方法的實(shí)現(xiàn)者,我在編碼時(shí)發(fā)現(xiàn)可能會(huì)出現(xiàn)異常,那么首先我要明確,這個(gè)可能出現(xiàn)的異常我能不能自己處理,如果能自己處理, 那么就在方法內(nèi)部自己處理掉,如果不能自己處理,那么通知方法的調(diào)用者處理。舉例說明:

public static Class<&#63;> forName(String className)  
      throws ClassNotFoundException { 
  return forName0(className, true, ClassLoader.getCallerClassLoader()); 
} 

上面的代碼是JDK中Class類的forName()方法。作為JDK類庫(kù)的作者,在寫這個(gè)方法的時(shí)候,可能會(huì)出現(xiàn)異常, 也就是類加載不到。但是他不知道如何處理這個(gè)情況,因?yàn)樗恢勒{(diào)用這個(gè)方法的用戶是加載的什么類,可能是一個(gè)非常重要的類, 加載不成的話程序就只能停掉,也可能是一個(gè)不那么重要的類,加載不到也沒有嚴(yán)重影響。所以,如何處理這個(gè)情況,必須是由用戶決定。方法后面的throws ClassNotFoundException的意義是:這個(gè)方法可能出現(xiàn)ClassNotFoundException,你如果調(diào)用了這個(gè)方法,那么必須做好防范措施(用try-catch處理這個(gè)異常,或者再向上拋出)。如果站在方法使用者的角度,我調(diào)用這個(gè)方法,如果出現(xiàn)異常,我可以提前準(zhǔn)備好解決方案:

try { 
  Class clazz = Class.forName("com.bjpowernode.Person"); 
} catch (ClassNotFoundException e) { 
   
  System.out.println("Person類加載失敗"); 
  System.exit(0); 
  e.printStackTrace(); 
}

 Person類是一個(gè)非常中要的類,必須加載成功才能繼續(xù)執(zhí)行。如果加載失敗, 只能讓程序停掉,并且打印出日志。這樣的話,程序員可以在其他地方確保這個(gè)類必須是可加載的。
所以,可以把編譯時(shí)受檢查異??醋鲆环N錯(cuò)誤預(yù)警機(jī)制:這個(gè)錯(cuò)誤可能發(fā)生, 但也可能不發(fā)生,但是如果你想使用這個(gè)功能的話,必須做好處理措施,可以使用try-catch處理異常, 也可以拋向更高層。

說完了編譯時(shí)受檢查異常,那么在談運(yùn)行時(shí)異常, 所有運(yùn)行時(shí)異常的頂層父類都是RuntimeException, RuntimeException也是繼承自Exception的。下面是JDK文檔中對(duì)運(yùn)行時(shí)異常的解釋。

1.RuntimeException 是那些可能在 Java 虛擬機(jī)正常運(yùn)行期間拋出的異常的超類。   

3.可能在執(zhí)行方法期間拋出但未被捕獲的 RuntimeException 的任何子類都無需在 throws 子句中進(jìn)行聲明。  

也就是說, 如果你在方法中拋出了運(yùn)行時(shí)異常或者其子類,那么可以不必在方法上聲明會(huì)拋出異常,所以調(diào)用這個(gè)方法的調(diào)用者也就不必在使用的時(shí)候做預(yù)防措施。那么在異常發(fā)生的時(shí)候,由于沒有處理措施,那么只能讓虛擬機(jī)停掉,也就是說這種異常一般不需要提前預(yù)防。那么什么時(shí)候使用運(yùn)行時(shí)異常呢?可以這樣認(rèn)為:如果發(fā)生了這樣一個(gè)異常時(shí),讓程序停掉是合理的,那么這種情況就適合使用運(yùn)行時(shí)異常。

還是以上面旅行的例子做一個(gè)說明。如果手機(jī)在旅途沒電了,那么預(yù)防這種情況是有意義的,因?yàn)閾Q了電池之后還可以繼續(xù)旅行;突然下雨這種情況也可預(yù)防,并且預(yù)防這種情況是有意義的,因?yàn)榇蚱饌銇硗瑯涌梢岳^續(xù)前進(jìn)。那么,如果如果在旅途中病了,并且病的還很厲害,那么再預(yù)防這種情況對(duì)整個(gè)旅程來說就沒有什么意義了,因?yàn)槁贸瘫仨毥K止(看病要緊)。所以直接拋出一個(gè)運(yùn)行時(shí)異常讓旅程終止。如下:

private static boolean isSick = true; 
public static void main(String[] args) { 
  if(isSick){ 
    System.out.println("生病了,旅途中止"); 
    throw new SickException("病了"); 
  } 
} 
private static class SickException extends RuntimeException{ 
  public SickException(String msg){ 
    super(msg); 
  } 
} 

 一般來說,運(yùn)行時(shí)異常非常適合處理編程錯(cuò)誤,那么什么是編程錯(cuò)誤呢?可以認(rèn)為是程序員寫的代碼有問題,必須修改程序才能解決問題。看一下JDK中的兩個(gè)RuntimeException的例子。

IllegalArgumentException:如果用戶(方法的調(diào)用者)傳遞的參數(shù)不對(duì),那么就會(huì)拋出非法參數(shù)異常,然后讓程序停掉,如果想讓程序正確的運(yùn)行,必須修改調(diào)用方式,傳遞一個(gè)正確的參數(shù)。如下:

public static void main(String[] args) { 
  caculateSalary(3); 
} 
 
/** 
 * 計(jì)算一個(gè)月的薪資 
 * @param month 月份 
 */ 
public static void caculateSalary(int month){ 
   
  //如果參數(shù)錯(cuò)誤, 拋出非法參數(shù)異常 
  if(month < 1 || month > 12){ 
    throw new IllegalArgumentException(); 
  } 
} 
 
private static void caculateSalaryInner(int month){ 
  //計(jì)算薪資 ... 
} 

NullPointerException:如果調(diào)用一個(gè)方法的對(duì)象為null,那么在調(diào)用的時(shí)候會(huì)拋出空指針異常。如果要避免的話,就要修改程序,確保調(diào)用方法的對(duì)象不為空。

ClassCastException:如果在進(jìn)行類型轉(zhuǎn)換時(shí),指定了錯(cuò)誤的目標(biāo)類型,那么會(huì)拋出類型轉(zhuǎn)換異常。如果要避免的話,要修改代碼,以確保指定了正確的要轉(zhuǎn)換的目標(biāo)類型。

雖然RuntimeException一般用于表示編程錯(cuò)誤,在拋出運(yùn)行時(shí)異常時(shí)讓程序停掉,對(duì)代碼做一定的改正以讓程序可以再次正確運(yùn)行, 但是要注意到,運(yùn)行時(shí)異常是可以捕獲的,捕獲之后做出處理后,程序可以恢復(fù)執(zhí)行:

public static void main(String[] args) { 
  doSomething(); 
} 
public static void doSomething(){ 
  Object obj = null; 
  try { //運(yùn)行時(shí)異常也是可以捕獲的 
    obj.toString(); 
  } catch (RuntimeException e) { 
    System.out.println("拋出了運(yùn)行時(shí)異常, 異常的具體類型:" + e.getClass().getName()); 
  } 
} 

打印結(jié)果為: 拋出了運(yùn)行時(shí)異常, 異常的具體類型:java.lang.NullPointerException

另外,運(yùn)行時(shí)異常也可以在方法上聲明拋出,但是如果方法上聲明的是運(yùn)行時(shí)異常,那么方法的調(diào)用者可以選擇處理, 也可以選擇不處理。如果不處理的話,程序會(huì)終止,如果捕獲后做出處理,程序可以恢復(fù)運(yùn)行:

public static void main(String[] args) { 
  doSomething();  //不必處理方法聲明拋出的運(yùn)行時(shí)異常 
} 
 
public static void doSomething() throws RuntimeException{ 
   
  throw new RuntimeException(); 
} 

雖然運(yùn)行時(shí)異??梢栽诜椒ㄉ下暶鲯伋觯部梢员徊东@,但是一般情況下我們不會(huì)這么做。因?yàn)檫\(yùn)行時(shí)異常一般用于表示編程錯(cuò)誤,出現(xiàn)異常時(shí)讓程序停掉是合理的。對(duì)運(yùn)行時(shí)異常進(jìn)行捕獲和聲明拋出沒有多大的意義。比如捕獲了空指針異常,雖然進(jìn)行了處理以讓程序不至于崩潰,但是空對(duì)象要調(diào)用的方法,根本就沒有調(diào)用成功,這是不合理的。

如何合理使用異常

上面介紹了異常的定義和分類,也提到了一些異常的使用原則?,F(xiàn)在總結(jié)一下到底應(yīng)該如何使用異常:

1 重大的錯(cuò)誤使用Error。一般Error用于表示系統(tǒng)級(jí)別的或虛擬機(jī)層面上的錯(cuò)誤,在編程中很少使用。

2 有必要預(yù)防,并且處理后可以讓程序恢復(fù)執(zhí)行的情況使用編譯時(shí)受檢查異常。

3 編程錯(cuò)誤使用運(yùn)行時(shí)異常。

4 如果方法自己可以處理異常,那么可以選擇自己處理異常,如果方法不知道如何處理異常,那么拋給高層的方法調(diào)用者。

5 方法聲明拋向高層的異常,必須是對(duì)高層有意義并且高層能夠理解的異常。

下面再舉一個(gè)趣味性的例子。

老板派員工出去執(zhí)行一項(xiàng)任務(wù),在這個(gè)過程中有兩個(gè)角色,員工是低層被調(diào)用者,老板是高層調(diào)用者。在這個(gè)過程中可能出現(xiàn)這么幾種情況。

1 老板讓員工出去執(zhí)行一項(xiàng)任務(wù), 那么必須得給撥款(沒錢干不成事嘛)。那么如果老板沒給錢,或者給的錢不夠,那么員工可以選擇停止執(zhí)行。這屬于編程錯(cuò)誤,要求老板必須給足夠的錢才能繼續(xù)運(yùn)行。這種情況使用運(yùn)行時(shí)異常表示。

2 到了目的地后,要去辦公地點(diǎn),發(fā)現(xiàn)迷路了(可能方向感不好,轉(zhuǎn)向了),找不到公交車的站牌了。這個(gè)錯(cuò)誤自己完全可以解決,打個(gè)車就可以了。并且不能拋給老板,如果拋給老板,那么就等著被炒魷魚吧。老板每天很忙,他會(huì)這樣認(rèn)為:這員工太操蛋了,這點(diǎn)事都辦不成。所以這是個(gè)受檢查異常,并且適合在內(nèi)部解決。

3 出差在外,加班太辛苦,在干到一半的時(shí)候,累病了。這就是比較嚴(yán)重的情況了,自己不能很好地解決(得去醫(yī)院)。這也是可以預(yù)見的異常,畢竟人都會(huì)得病嘛。這也屬于受檢查異常,自己不能解決,得拋向高層(老板)。但是應(yīng)該怎樣給老板說呢?不能給老板說“老板,我病了”,如果這樣給老板說的話,老板會(huì)一頭霧水:“病了去醫(yī)院啊, 我又不是醫(yī)生”。那么怎么給老板說呢?直接告訴老板任務(wù)不能完成就行了,當(dāng)然可以說明為什么不能完成的原因(生病了)。這樣的話,老板就可以做出一些處理,可以另外再拍一個(gè)人去交接任務(wù),也可以決定暫停任務(wù)。所以,拋向高層的異常,必須是對(duì)高層有意義并且高層能夠理解的異常。

下面用代碼描述這個(gè)過程:

public class DoWork { 
  public static class Boss{  //老板 
    private Employee emp; //員工對(duì)象 
    public Boss(Employee emp){ 
      this.emp = emp; 
    } 
    public void doWork(){  
      try { 
        emp.doWork();  //老板委托員工外出執(zhí)行任務(wù),給員工塊錢的經(jīng)費(fèi) 
      } catch (TaskCannotCompleteException e) {  //任務(wù)無法完成 
        System.out.println("派出另一個(gè)員工去完成任務(wù)"); 
      }  
    } 
  } 
  public static class Employee{  //員工 
    //執(zhí)行任務(wù),可能不能完成任務(wù) 
    public void doWork(float money) throws TaskCannotCompleteException{ 
      //  
      if(money < ){  //經(jīng)費(fèi)太少,無法執(zhí)行任務(wù) 
        throw new MoneyNotEnoughException(); 
      } 
      // 
      try { 
        goToWorkPlace(); 
      } catch (CannotFindBusException e) { //在去工作地點(diǎn)時(shí)找不到公交車 
        System.out.println("打車去"); 
      } 
      // 
      try { 
        workDayAndNight(); 
      } catch (TiredToSickException e) { //累病了 
        //告訴老板,任務(wù)無法完成 
        throw new TaskCannotCompleteException(); 
      } 
    } 
    //在去工作地點(diǎn)時(shí)可能找不到公交車 
    private void goToWorkPlace() throws CannotFindBusException{ 
      //throw new CannotFindBusException(); 
    } 
    //沒天沒夜的干活, 可能會(huì)累病 
    private void workDayAndNight() throws TiredToSickException{ 
      //throw new TiredToSickException(); 
    } 
  } 
  //找不到公交車異常 
  public static class CannotFindBusException extends Exception{} 
  //經(jīng)費(fèi)不足異常 
  public static class MoneyNotEnoughException extends RuntimeException{} 
  //累病異常 
  public static class TiredToSickException extends Exception{} 
  //任務(wù)無法完成異常 
  public static class TaskCannotCompleteException extends Exception{} 
  public static void main(String[] args) { 
    Boss boss = new Boss(new Employee()); 
    boss.doWork(); 
  } 
} 

關(guān)于一文帶你快速讀懂Java中的異常就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI