異常、執行緒
主要內容
-
異常、執行緒
第一章 異常
1.1 異常概念
異常,就是不正常的意思。在生活中:醫生說,你的身體某個部位有異常,該部位和正常相比有點不同,該部位的功能將受影響.在程式中的意思就是:
-
異常 :指的是程式在執行過程中,出現的非正常的情況,最終會導致JVM的非正常停止。
在Java等面向物件的程式語言中,異常本身是一個類,產生異常就是建立異常物件並丟擲了一個異常物件。Java處理異常的方式是中斷處理。
異常指的並不是語法錯誤,語法錯了,編譯不通過,不會產生位元組碼檔案,根本不能執行.
1.2 異常體系
異常機制其實是幫助我們找到程式中的問題,異常的根類是java.lang.Throwable
java.lang.Error
與java.lang.Exception
,平常所說的異常指java.lang.Exception
。
Throwable體系:
-
Error:嚴重錯誤Error,無法通過處理的錯誤,只能事先避免,好比絕症。
-
Exception:表示異常,異常產生後程序員可以通過程式碼的方式糾正,使程式繼續執行,是必須要處理的。好比感冒、闌尾炎。
Throwable中的常用方法:
-
public void printStackTrace()
:列印異常的詳細資訊。包含了異常的型別,異常的原因,還包括異常出現的位置,在開發和除錯階段,都得使用printStackTrace。
-
public String getMessage()
:獲取發生異常的原因。提示給使用者的時候,就提示錯誤原因。
-
public String toString()
:獲取異常的型別和異常描述資訊(不用)。
出現異常,不要緊張,把異常的簡單類名,拷貝到API中去查。
1.3 異常分類
我們平常說的異常就是指Exception,因為這類異常一旦出現,我們就要對程式碼進行更正,修復程式。
異常(Exception)的分類:根據在編譯時期還是執行時期去檢查異常?
-
編譯時期異常:checked異常。在編譯時期,就會檢查,如果沒有處理異常,則編譯失敗。(如日期格式化異常)
-
執行時期異常
1.4 異常的產生過程解析
先執行下面的程式,程式會產生一個數組索引越界異常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
2.1 丟擲異常throw
在編寫程式時,我們必須要考慮程式出現問題的情況。比如,在定義方法時,方法需要接受引數。那麼,當呼叫方法使用接受到的引數時,首先需要先對引數資料進行合法的判斷,資料若不合法,就應該告訴呼叫者,傳遞合法的資料進來。這時需要使用丟擲異常的方式來告訴呼叫者。
在java中,提供了一個throw關鍵字,它用來丟擲一個指定的異常物件。那麼,丟擲一個異常具體如何操作呢?
-
建立一個異常物件。封裝一些提示資訊(資訊可以自己編寫)。
-
需要將這個異常物件告知給呼叫者。怎麼告知呢?怎麼將這個異常物件傳遞到呼叫者處呢?通過關鍵字throw就可以完成。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; } }
2.2 Objects非空判斷
還記得我們學習過一個類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; }
2.3 宣告異常throws
宣告異常:將問題標識出來,報告給呼叫者。如果方法內通過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(); } } }
2.4 捕獲異常try…catch
如果異常出現的話,會立刻終止程式,所以我們得處理異常:
-
該方法不處理,而是宣告丟擲,由該方法的呼叫者來處理(throws)。
-
在方法中使用try-catch的語句塊來處理異常。
try-catch的方式就是捕獲異常。
-
捕獲異常:Java中對異常有針對性的語句進行捕獲,可以對出現的異常進行指定方式的處理。
捕獲異常語法如下:
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。
2.4 finally 程式碼塊
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("檔案不存在"); } } }
2.5 異常注意事項
-
多個異常使用捕獲又該如何處理呢?
-
多個異常分別處理。
-
多個異常一次捕獲,多次處理。
-
多個異常一次捕獲一次處理。
一般我們是使用一次捕獲多次處理方式,格式如下:
-
try{ 編寫可能會出現異常的程式碼 }catch(異常型別A e){ 當try中出現A型別異常,就用該catch來捕獲. 處理異常的程式碼 //記錄日誌/列印異常資訊/繼續丟擲異常 }catch(異常型別B e){ 當try中出現B型別異常,就用該catch來捕獲. 處理異常的程式碼 //記錄日誌/列印異常資訊/繼續丟擲異常 }
-
注意:這種異常處理方式,要求多個catch中的異常不能相同,並且若catch中的多個異常之間有子父類異常的關係,那麼子類異常要求在上面的catch處理,父類異常在下面的catch處理。
-
執行時異常被丟擲可以不處理。即不捕獲也不宣告丟擲。
-
如果finally有return語句,永遠返回finally中的結果,避免該情況.
-
如果父類丟擲了多個異常,子類重寫父類方法時,丟擲和父類相同的異常或者是父類異常的子類或者不丟擲異常。
-
父類方法沒有丟擲異常,子類重寫父類該方法時也不可丟擲異常。此時子類產生該異常,只能捕獲處理,不能宣告丟擲
第三章 自定義異常
3.1 概述
為什麼需要自定義異常類:
我們說了Java中不同的異常類,分別表示著某一種具體的異常情況,那麼在開發中總是有些異常情況是SUN沒有定義好的,此時我們根據自己業務的異常情況來定義異常類。例如年齡負數問題,考試成績負數問題等等。
在上述程式碼中,發現這些異常都是JDK內部定義好的,但是實際開發中也會出現很多異常,這些異常很可能在JDK中沒有定義過,例如年齡負數問題,考試成績負數問題.那麼能不能自己定義異常呢?
什麼是自定義異常類:
在開發中根據自己業務的異常情況來定義異常類.
自定義一個業務邏輯異常: RegisterException。一個註冊異常類。
異常類如何定義:
-
自定義一個編譯期異常: 自定義類 並繼承於
java.lang.Exception
。 -
自定義一個執行時期的異常類:自定義類 並繼承於
java.lang.RuntimeException
。
3.2 自定義異常的練習
要求:我們模擬註冊操作,如果使用者名稱已存在,則丟擲異常並提示:親,該使用者名稱已經被註冊。
首先定義一個登陸異常類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; } }
第四章 多執行緒
我們在之前,學習的程式在沒有跳轉語句的前提下,都是由上至下依次執行,那現在想要設計一個程式,邊打遊戲邊聽歌,怎麼設計?
要解決上述問題,咱們得使用多程序或者多執行緒來解決.
4.1 併發與並行
-
併發:指兩個或多個事件在同一個時間段內發生。
-
並行:指兩個或多個事件在同一時刻發生(同時發生)。
在作業系統中,安裝了多個程式,併發指的是在一段時間內巨集觀上有多個程式同時執行,這在單 CPU 系統中,每一時刻只能有一道程式執行,即微觀上這些程式是分時的交替執行,只不過是給人的感覺是同時執行,那是因為分時交替執行的時間是非常短的。
而在多個 CPU 系統中,則這些可以併發執行的程式便可以分配到多個處理器上(CPU),實現多工並行執行,即利用每個處理器來處理一個可以併發執行的程式,這樣多個程式便可以同時執行。目前電腦市場上說的多核 CPU,便是多核處理器,核 越多,並行處理的程式越多,能大大的提高電腦執行的效率。
注意:單核處理器的計算機肯定是不能並行的處理多個任務的,只能是多個任務在單個CPU上併發執行。同理,執行緒也是一樣的,從巨集觀角度上理解執行緒是並行執行的,但是從微觀角度上分析卻是序列執行的,即一個執行緒一個執行緒的去執行,當系統只有一個CPU時,執行緒會以某種順序執行多個執行緒,我們把這種情況稱之為執行緒排程。
4.2 執行緒與程序
-
程序:是指一個記憶體中執行的應用程式,每個程序都有一個獨立的記憶體空間,一個應用程式可以同時執行多個程序;程序也是程式的一次執行過程,是系統執行程式的基本單位;系統執行一個程式即是一個程序從建立、執行到消亡的過程。
-
執行緒:執行緒是程序中的一個執行單元,負責當前程序中程式的執行,一個程序中至少有一個執行緒。一個程序中是可以有多個執行緒的,這個應用程式也可以稱之為多執行緒程式。
簡而言之:一個程式執行後至少有一個程序,一個程序中可以包含多個執行緒
我們可以再電腦底部工作列,右鍵----->開啟工作管理員,可以檢視當前任務的程序:
程序
執行緒
執行緒排程:
-
分時排程
所有執行緒輪流使用 CPU 的使用權,平均分配每個執行緒佔用 CPU 的時間。
-
搶佔式排程
優先讓優先順序高的執行緒使用 CPU,如果執行緒的優先順序相同,那麼會隨機選擇一個(執行緒隨機性),Java使用的為搶佔式排程。
-
設定執行緒的優先順序
-
搶佔式排程詳解
大部分作業系統都支援多程序併發執行,現在的作業系統幾乎都支援同時執行多個程式。比如:現在我們上課一邊使用編輯器,一邊使用錄屏軟體,同時還開著畫圖板,dos視窗等軟體。此時,這些程式是在同時執行,”感覺這些軟體好像在同一時刻執行著“。
4.3 建立執行緒類
Java使用java.lang.Thread
類代表執行緒,所有的執行緒物件都必須是Thread類或其子類的例項。每個執行緒的作用是完成一定的任務,實際上就是執行一段程式流即一段順序執行的程式碼。Java使用執行緒執行體來代表這段程式流。Java中通過繼承Thread類來建立並啟動多執行緒的步驟如下:
-
定義Thread類的子類,並重寫該類的run()方法,該run()方法的方法體就代表了執行緒需要完成的任務,因此把run()方法稱為執行緒執行體。
-
建立Thread子類的例項,即建立了執行緒物件
-
呼叫執行緒物件的start()方法來啟動該執行緒
程式碼如下:
測試類:
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); } } }