異常,就是不正常的意思。在生活中:醫生說,你的身體某個部位有異常,該部位和正常相比有點不一樣,該部位的功能將受影響.在程序中的意思就是:java
在Java等面向對象的編程語言中,異常自己是一個類,產生異常就是建立異常對象並拋出了一個異常對象。Java處理異常的方式是中斷處理。程序員
異常指的並非語法錯誤,語法錯了,編譯不經過,不會產生字節碼文件,根本不能運行.
異常機制實際上是幫助咱們找到程序中的問題,異常的根類是java.lang.Throwable
,其下有兩個子類:java.lang.Error
與java.lang.Exception
,日常所說的異常指java.lang.Exception
。數據庫
Throwable體系:編程
Throwable中的經常使用方法:數組
public void printStackTrace()
:打印異常的詳細信息。包含了異常的類型,異常的緣由,還包括異常出現的位置,在開發和調試階段,都得使用printStackTrace。安全
public String getMessage()
:獲取發生異常的緣由。提示給用戶的時候,就提示錯誤緣由。網絡
public String toString()
:獲取異常的類型和異常描述信息(不用)。出現異常,沒關係張,把異常的簡單類名,拷貝到API中去查。多線程
咱們日常說的異常就是指Exception,由於這類異常一旦出現,咱們就要對代碼進行更正,修復程序。併發
異常(Exception)的分類:根據在編譯時期仍是運行時期去檢查異常?編程語言
先運行下面的程序,程序會產生一個數組索引越界異常ArrayIndexOfBoundsException。咱們經過圖解來解析下異常產生的過程。
工具類
public class ArrayTools { // 對給定的數組經過給定的角標獲取元素。 public static int getElement(int[] arr, int index) { int element = arr[index]; return element; } }
測試類
public class ExceptionDemo { public static void main(String[] args) { int[] arr = { 34, 12, 67 }; intnum = ArrayTools.getElement(arr, 4) System.out.println("num=" + num); System.out.println("over"); } }
上述程序執行過程圖解:
Java異常處理的五個關鍵字:try、catch、finally、throw、throws
在編寫程序時,咱們必需要考慮程序出現問題的狀況。好比,在定義方法時,方法須要接受參數。那麼,當調用方法使用接受到的參數時,首先須要先對參數數據進行合法的判斷,數據若不合法,就應該告訴調用者,傳遞合法的數據進來。這時須要使用拋出異常的方式來告訴調用者。
在java中,提供了一個throw關鍵字,它用來拋出一個指定的異常對象。那麼,拋出一個異常具體如何操做呢?
throw用在方法內,用來拋出一個異常對象,將這個異常對象傳遞到調用者處,並結束當前方法的執行。
使用格式:
throw new 異常類名(參數);
例如:
throw new NullPointerException("要訪問的arr數組不存在"); throw new ArrayIndexOutOfBoundsException("該索引在數組中不存在,已超出範圍");
學習完拋出異常的格式後,咱們經過下面程序演示下throw的使用。
public class ThrowDemo { public static void main(String[] args) { //建立一個數組 int[] arr = {2,4,52,2}; //根據索引找對應的元素 int index = 4; int element = getElement(arr, index); System.out.println(element); System.out.println("over"); } /* * 根據 索引找到數組中對應的元素 */ public static int getElement(int[] arr,int index){ //判斷 索引是否越界 if(index<0 || index>arr.length-1){ /* 判斷條件若是知足,當執行完throw拋出異常對象後,方法已經沒法繼續運算。 這時就會結束當前方法的執行,並將異常告知給調用者。這時就須要經過異常來解決。 */ throw new ArrayIndexOutOfBoundsException("哥們,角標越界了~~~"); } int element = arr[index]; return element; } }
注意:若是產生了問題,咱們就會throw將問題描述類即異常進行拋出,也就是將問題返回給該方法的調用者。那麼對於調用者來講,該怎麼處理呢?一種是進行捕獲處理,另外一種就是繼續講問題聲明出去,使用throws聲明處理。
還記得咱們學習過一個類Objects嗎,曾經提到過它由一些靜態的實用方法組成,這些方法是null-save(空指針安全的)或null-tolerant(容忍空指針的),那麼在它的源碼中,對對象爲null的值進行了拋出異常操做。
public static <T> T requireNonNull(T obj)
:查看指定引用對象不是null。查看源碼發現這裏對爲null的進行了拋出異常操做:
public static <T> T requireNonNull(T obj) { if (obj == null) throw new NullPointerException(); return obj; }
聲明異常:將問題標識出來,報告給調用者。若是方法內經過throw拋出了編譯時異常,而沒有捕獲處理(稍後講解該方式),那麼必須經過throws進行聲明,讓調用者去處理。
關鍵字throws運用於方法聲明之上,用於表示當前方法不處理異常,而是提醒該方法的調用者來處理異常(拋出異常).
聲明異常格式:
修飾符 返回值類型 方法名(參數) throws 異常類名1,異常類名2…{ }
聲明異常的代碼演示:
public class ThrowsDemo { public static void main(String[] args) throws FileNotFoundException { read("a.txt"); } // 若是定義功能時有問題發生須要報告給調用者。能夠經過在方法上使用throws關鍵字進行聲明 public static void read(String path) throws FileNotFoundException { if (!path.equals("a.txt")) {//若是不是 a.txt這個文件 // 我假設 若是不是 a.txt 認爲 該文件不存在 是一個錯誤 也就是異常 throw throw new FileNotFoundException("文件不存在"); } } }
throws用於進行異常類的聲明,若該方法可能有多種異常狀況產生,那麼在throws後面能夠寫多個異常類,用逗號隔開。
public class ThrowsDemo2 { public static void main(String[] args) throws IOException { read("a.txt"); } public static void read(String path)throws FileNotFoundException, IOException { if (!path.equals("a.txt")) {//若是不是 a.txt這個文件 // 我假設 若是不是 a.txt 認爲 該文件不存在 是一個錯誤 也就是異常 throw throw new FileNotFoundException("文件不存在"); } if (!path.equals("b.txt")) { throw new IOException(); } } }
若是異常出現的話,會馬上終止程序,因此咱們得處理異常:
try-catch的方式就是捕獲異常。
捕獲異常語法以下:
try{ 編寫可能會出現異常的代碼 }catch(異常類型 e){ 處理異常的代碼 //記錄日誌/打印異常信息/繼續拋出異常 }
try:該代碼塊中編寫可能產生異常的代碼。
catch:用來進行某種異常的捕獲,實現對捕獲到的異常進行處理。
注意:try和catch都不能單獨使用,必須連用。
演示以下:
public class TryCatchDemo { public static void main(String[] args) { try {// 當產生異常時,必須有處理方式。要麼捕獲,要麼聲明。 read("b.txt"); } catch (FileNotFoundException e) {// 括號中須要定義什麼呢? //try中拋出的是什麼異常,在括號中就定義什麼異常類型 System.out.println(e); } System.out.println("over"); } /* * * 咱們 當前的這個方法中 有異常 有編譯期異常 */ public static void read(String path) throws FileNotFoundException { if (!path.equals("a.txt")) {//若是不是 a.txt這個文件 // 我假設 若是不是 a.txt 認爲 該文件不存在 是一個錯誤 也就是異常 throw throw new FileNotFoundException("文件不存在"); } } }
如何獲取異常信息:
Throwable類中定義了一些查看方法:
public String getMessage()
:獲取異常的描述信息,緣由(提示給用戶的時候,就提示錯誤緣由。public String toString()
:獲取異常的類型和異常描述信息(不用)。public void printStackTrace()
:打印異常的跟蹤棧信息並輸出到控制檯。 包含了異常的類型,異常的緣由,還包括異常出現的位置,在開發和調試階段,都得使用printStackTrace。
finally:有一些特定的代碼不管異常是否發生,都須要執行。另外,由於異常會引起程序跳轉,致使有些語句執行不到。而finally就是解決這個問題的,在finally代碼塊中存放的代碼都是必定會被執行的。
何時的代碼必須最終執行?
當咱們在try語句塊中打開了一些物理資源(磁盤文件/網絡鏈接/數據庫鏈接等),咱們都得在使用完以後,最終關閉打開的資源。
finally的語法:
try...catch....finally:自身須要處理異常,最終還得關閉資源。
注意:finally不能單獨使用。
好比在咱們以後學習的IO流中,當打開了一個關聯文件的資源,最後程序無論結果如何,都須要把這個資源關閉掉。
finally代碼參考以下:
public class TryCatchDemo4 { public static void main(String[] args) { try { read("a.txt"); } catch (FileNotFoundException e) { //抓取到的是編譯期異常 拋出去的是運行期 throw new RuntimeException(e); } finally { System.out.println("無論程序怎樣,這裏都將會被執行。"); } System.out.println("over"); } /* * * 咱們 當前的這個方法中 有異常 有編譯期異常 */ public static void read(String path) throws FileNotFoundException { if (!path.equals("a.txt")) {//若是不是 a.txt這個文件 // 我假設 若是不是 a.txt 認爲 該文件不存在 是一個錯誤 也就是異常 throw throw new FileNotFoundException("文件不存在"); } } }
當只有在try或者catch中調用退出JVM的相關方法,此時finally纔不會執行,不然finally永遠會執行。
多個異常使用捕獲又該如何處理呢?
通常咱們是使用一次捕獲屢次處理方式,格式以下:
try{ 編寫可能會出現異常的代碼 }catch(異常類型A e){ 當try中出現A類型異常,就用該catch來捕獲. 處理異常的代碼 //記錄日誌/打印異常信息/繼續拋出異常 }catch(異常類型B e){ 當try中出現B類型異常,就用該catch來捕獲. 處理異常的代碼 //記錄日誌/打印異常信息/繼續拋出異常 }
注意:這種異常處理方式,要求多個catch中的異常不能相同,而且若catch中的多個異常之間有子父類異常的關係,那麼子類異常要求在上面的catch處理,父類異常在下面的catch處理。
爲何須要自定義異常類:
咱們說了Java中不一樣的異常類,分別表示着某一種具體的異常狀況,那麼在開發中老是有些異常狀況是SUN沒有定義好的,此時咱們根據本身業務的異常狀況來定義異常類。例如年齡負數問題,考試成績負數問題等等。
在上述代碼中,發現這些異常都是JDK內部定義好的,可是實際開發中也會出現不少異常,這些異常極可能在JDK中沒有定義過,例如年齡負數問題,考試成績負數問題.那麼能不能本身定義異常呢?
什麼是自定義異常類:
在開發中根據本身業務的異常狀況來定義異常類.
自定義一個業務邏輯異常: RegisterException。一個註冊異常類。
異常類如何定義:
java.lang.Exception
。java.lang.RuntimeException
。要求:咱們模擬註冊操做,若是用戶名已存在,則拋出異常並提示:親,該用戶名已經被註冊。
首先定義一個登錄異常類RegisterException:
// 業務邏輯異常 public class RegisterException extends Exception { /** * 空參構造 */ public RegisterException() { } /** * * @param message 表示異常提示 */ public RegisterException(String message) { super(message); } }
模擬登錄操做,使用數組模擬數據庫中存儲的數據,並提供當前註冊帳號是否存在方法用於判斷。
public class Demo { // 模擬數據庫中已存在帳號 private static String[] names = {"bill","hill","jill"}; public static void main(String[] args) { //調用方法 try{ // 可能出現異常的代碼 checkUsername("nill"); System.out.println("註冊成功");//若是沒有異常就是註冊成功 }catch(RegisterException e){ //處理異常 e.printStackTrace(); } } //判斷當前註冊帳號是否存在 //由於是編譯期異常,又想調用者去處理 因此聲明該異常 public static boolean checkUsername(String uname) throws LoginException{ for (String name : names) { if(name.equals(uname)){//若是名字在這裏面 就拋出登錄異常 throw new RegisterException("親"+name+"已經被註冊了!"); } } return true; } }
咱們在以前,學習的程序在沒有跳轉語句的前提下,都是由上至下依次執行,那如今想要設計一個程序,邊打遊戲邊聽歌,怎麼設計?
要解決上述問題,我們得使用多進程或者多線程來解決.
在操做系統中,安裝了多個程序,併發指的是在一段時間內宏觀上有多個程序同時運行,這在單 CPU 系統中,每一時刻只能有一道程序執行,即微觀上這些程序是分時的交替運行,只不過是給人的感受是同時運行,那是由於分時交替運行的時間是很是短的。
而在多個 CPU 系統中,則這些能夠併發執行的程序即可以分配到多個處理器上(CPU),實現多任務並行執行,即利用每一個處理器來處理一個能夠併發執行的程序,這樣多個程序即可以同時執行。目前電腦市場上說的多核 CPU,即是多核處理器,核 越多,並行處理的程序越多,能大大的提升電腦運行的效率。
注意:單核處理器的計算機確定是不能並行的處理多個任務的,只能是多個任務在單個CPU上併發運行。同理,線程也是同樣的,從宏觀角度上理解線程是並行運行的,可是從微觀角度上分析倒是串行運行的,即一個線程一個線程的去運行,當系統只有一個CPU時,線程會以某種順序執行多個線程,咱們把這種狀況稱之爲線程調度。
簡而言之:一個程序運行後至少有一個進程,一個進程中能夠包含多個線程
咱們能夠再電腦底部任務欄,右鍵----->打開任務管理器,能夠查看當前任務的進程:
進程
線程
線程調度:
全部線程輪流使用 CPU 的使用權,平均分配每一個線程佔用 CPU 的時間。
搶佔式調度
優先讓優先級高的線程使用 CPU,若是線程的優先級相同,那麼會隨機選擇一個(線程隨機性),Java使用的爲搶佔式調度。
大部分操做系統都支持多進程併發運行,如今的操做系統幾乎都支持同時運行多個程序。好比:如今咱們上課一邊使用編輯器,一邊使用錄屏軟件,同時還開着畫圖板,dos窗口等軟件。此時,這些程序是在同時運行,」感受這些軟件好像在同一時刻運行着「。
實際上,CPU(中央處理器)使用搶佔式調度模式在多個線程間進行着高速的切換。對於CPU的一個核而言,某個時刻,只能執行一個線程,而 CPU的在多個線程間切換速度相對咱們的感受要快,看上去就是在同一時刻運行。
其實,多線程程序並不能提升程序的運行速度,但可以提升程序運行效率,讓CPU的使用率更高。
Java使用java.lang.Thread
類表明線程,全部的線程對象都必須是Thread類或其子類的實例。每一個線程的做用是完成必定的任務,實際上就是執行一段程序流即一段順序執行的代碼。Java使用線程執行體來表明這段程序流。Java中經過繼承Thread類來建立並啓動多線程的步驟以下:
代碼以下:
測試類:
public class Demo01 { public static void main(String[] args) { //建立自定義線程對象 MyThread mt = new MyThread("新的線程!"); //開啓新線程 mt.start(); //在主方法中執行for循環 for (int i = 0; i < 10; i++) { System.out.println("main線程!"+i); } } }
自定義線程類:
public class MyThread extends Thread { //定義指定線程名稱的構造方法 public MyThread(String name) { //調用父類的String參數的構造方法,指定線程的名稱 super(name); } /** * 重寫run方法,完成該線程執行的邏輯 */ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(getName()+":正在執行!"+i); } } }