文章目錄
全部源碼:https://github.com/name365/JavaSE-30Day
轉載自atguigu.com視頻
第五章 異常處理
異常概述與異常體系結構
在使用計算機語言進行項目開發的過程中,即使程序員把代碼寫得盡善盡美,在系統的運行過程中仍然會遇到一些問題,因爲很多問題不是靠代碼能夠避免的,比如:客戶輸入數據的格式,讀取文件是否存在,網絡是否始終保持通暢等等。
-
異常:在Java語言中,將程序執行中發生的不正常情況稱爲“異常”。(開發過程中的語法錯誤和邏輯錯誤不是異常)
-
Java程序在執行過程中所發生的異常事件可分爲兩類:
-
Error:Java虛擬機無法解決的嚴重問題。如:JVM系統內部錯誤、資源耗盡等嚴重情況。比如:StackOverflowError和OOM。一般不編寫針對性的代碼進行處理。
/* * Java虛擬機無法解決的嚴重問題。如:JVM系統內部錯誤、資源耗盡等嚴重情況。 * 比如:StackOverflowError和OOM。 * 一般不編寫針對性的代碼進行處理。 * */ public class ErrorTest { public static void main(String[] args) { //1.棧溢出:java.lang.StackOverflowError // main(args); //2.堆溢出:java.lang.OutOfMemoryError // Integer[] arr = new Integer[1024*1024*1024]; } }
-
Exception:其它因編程錯誤或偶然的外在因素導致的一般性問題,可以使用針對性的代碼進行處理。例如:
-
空指針訪問
-
試圖讀取不存在的文件
-
網絡連接中斷
-
數組角標越界
-
-
-
對於這些錯誤,一般有兩種解決方法:一是遇到錯誤就終止程序的運行。另一種方法是由程序員在編寫程序時,就考慮到錯誤的檢測、錯誤消息的提示,以及錯誤的處理。
-
捕獲錯誤最理想的是在編譯期間,但有的錯誤只有在運行時纔會發生。比如:除數爲0,數組下標越界等
- 分類:編譯時異常和運行時異常
-
運行時異常
- 是指編譯器不要求強制處置的異常。一般是指編程時的邏輯錯誤,是程序員應該積極避免其出現的異常。java.lang.RuntimeException類及它的子類都是運行時異常。
- 對於這類異常,可以不作處理,因爲這類異常很普遍,若全處理可能會對程序的可讀性和運行效率產生影響。
-
編譯時異常
- 是指編譯器要求必須處置的異常。即程序在運行時由於外界因素造成的一般性異常。編譯器要求Java程序必須捕獲或聲明所有編譯時異常。
- 對於這類異常,如果程序不處理,可能會帶來意想不到的結果。
常見異常
import java.io.File;
import java.io.FileInputStream;
import java.util.Date;
import java.util.Scanner;
import org.junit.Test;
/*
* 一、java異常體系結構
*
* java.lang.Throwable
* |----java.lang.Error:一般不編寫針對性的代碼進行處理
* |----java.lang.Exception:可以進行異常處理
* |----編譯時異常(checked)
* |----IOEXception
* |----FileNotFoundException
* |----ClassNotFoundException
* |----運行時異常(unchecked)
* |----NullPointerException
* |----ArrayIndexOutOfBoundsException
* |----ClassCaseException
* |----NumberFormatException
* |----InputMismatchException
* |----ArithmaticException
*
* 面試題:常見的異常有哪些?舉例說明
*
*/
public class ExceptionTest {
// ******************以下是編譯時異常***************************
@Test
public void test7() {
// File file = new File("hello.txt");
// FileInputStream fis = new FileInputStream(file);
//
// int data = fis.read();
// while(data != -1){
// System.out.print((char)data);
// data = fis.read();
// }
//
// fis.close();
}
// ******************以下是運行時異常***************************
// ArithmeticException
@Test
public void test6() {
int a = 10;
int b = 0;
System.out.println(a / b);
}
// InputMismatchException
@Test
public void test5() {
Scanner scanner = new Scanner(System.in);
int score = scanner.nextInt();
System.out.println(score);
scanner.close();
}
// NumberFormatException
@Test
public void test4() {
String str = "123";
str = "abc";
int num = Integer.parseInt(str);
}
// ClassCaseException
@Test
public void test3() {
Object obj = new Date();
String str = (String)obj;
}
// ArrayIndexOutOfBoundsException
@Test
public void test2() {
// int[] arr = new int[10];
// System.out.println(arr[10]);
// String str = "abc";
// System.out.println(str.charAt(3));
}
// NullPointerException
@Test
public void test1() {
// int[] arr = null;
// System.out.println(arr[3]);
// String str = "abc";
// str = null;
// System.out.println(str.charAt(0));
}
}
異常處理機制一:try-catch-finally
在編寫程序時,經常要在可能出現錯誤的地方加上檢測的代碼,如進行x/y運算時,要檢測分母爲0,數據爲空,輸入的不是數據而是字符等。過多的if-else分支會導致程序的代碼加長、臃腫,可讀性差。因此採用異常處理機制。
Java異常處理:
Java採用的異常處理機制,是將異常處理的程序代碼集中在一起,與正常的程序代碼分開,使得程序簡潔、優雅,並易於維護。
Java異常處理的方式:
方式一:try-catch-finally
方式二:throws + 異常類型
-
try
- 捕獲異常的第一步是用try{…}語句塊選定捕獲異常的範圍,將可能出現異常的代碼放在try語句塊中。
-
catch(Exceptiontypee)
-
在catch語句塊中是對異常對象進行處理的代碼。每個try語句塊可以伴隨一個或多個catch語句,用於處理可能產生的不同類型的異常對象。
-
捕獲異常的有關信息:與其它對象一樣,可以訪問一個異常對象的成員變量或調用它的方法。
- getMessage() 獲取異常信息,返回字符串
- printStackTrace() 獲取異常類名和異常信息,以及異常出現在程序中的位置。返回值void。
-
finally
- 捕獲異常的最後一步是通過finally語句爲異常處理提供一個統一的出口,使得在控制流轉到程序的其它部分以前,能夠對程序的狀態作統一的管理。
- 不論在try代碼塊中是否發生了異常事件,catch語句是否執行,catch語句是否有異常,catch語句中是否有return,finally塊中的語句都會被執行。
- finally語句和catch語句是任選的
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import org.junit.Test;
/*
* 異常的處理:抓拋模型
*
* 過程一:“拋”:程序在征程執行過程中,一旦出現異常,就會在異常代碼處生成一個對應異常類的對象
* 並將此對象拋出。
* 一旦拋出對象以後,其後的代碼就不再執行。
*
* 過程二:“抓”:可以理解爲異常的處理方式:① try-catch-finally ② throws
*
* 二、try-catch-finally的使用
*
* try{
* //可能出現異常的代碼
* }catch(異常類型1 變量名1){
* //處理異常的方式1
* }catch(異常類型2 變量名2){
* //處理異常的方式2
* }catch(異常類型3 變量名3){
* //處理異常的方式3
* }
* ...
* finally{
* //一定會執行的代碼
* }
*
* 說明:
* 1.finally是可選的。
* 2.使用try將可能出現異常代碼包裝起來,在執行過程中,一旦出現異常,就會生成一個對應異常類的對象,根據此對象
* 的類型,去catch中進行匹配。
* 3.一旦try中的異常對象匹配到某一個catch時,就進入catch中進行異常的處理。一旦處理完成,就跳出當前的
* try-catch結構(在沒有寫finally的情況)。繼續執行其後的代碼。
* 4.catch中的異常類型如果沒有子父類關係,則誰聲明在上,誰聲明在下無所謂。
* catch中的異常類型如果滿足子父類關係,則要求子類一定聲明在父類的上面。否則,報錯
* 5.常用的異常對象處理的方式: ① String getMessage() ② printStackTrace()
* 6.在try結構中聲明的變量,再出了try結構以後,就不能再被調用,例65行:System.out.println(num);
* 7.try-catch-finally結構可以嵌套
*
* 體會1:使用try-catch-finally處理編譯時異常,是得程序在編譯時就不再報錯,但是運行時仍可能報錯。
* 相當於我們使用try-catch-finally將一個編譯時可能出現的異常,延遲到運行時出現。
*
* 體會2:開發中,由於運行時異常比較常見,所以我們通常就不針對運行時異常編寫try-catch-finally了。
* 針對於編譯時異常,我們說一定要考慮異常的處理。
*/
public class ExceptionTest1 {
@Test
public void test2(){
try{
File file = new File("hello.txt");
FileInputStream fis = new FileInputStream(file);
int data = fis.read();
while(data != -1){
System.out.print((char)data);
data = fis.read();
}
fis.close();
}catch(FileNotFoundException e){
e.printStackTrace();
}catch(IOException e){
e.printStackTrace();
}
}
@Test
public void test1(){
String str = "123";
str = "abc";
try{
int num = Integer.parseInt(str);
System.out.println("hello-----1");
}catch(NumberFormatException e){
// System.out.println("出現數值轉換異常了,不要着急....");
//String getMessage():
// System.out.println(e.getMessage());
//printStackTrace():
e.printStackTrace();
}catch(NullPointerException e){
System.out.println("出現空指針異常了,不要着急....");
}catch(Exception e){
System.out.println("出現異常了,不要着急....");
}
// System.out.println(num);
System.out.println("hello----2");
}
}
finally的使用
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import org.junit.Test;
/*
* try-catch-finally中finally的使用:
*
* 1.finally是可選的。
* 2.finally中聲明的是一定會被執行的代碼。即使catch中又出現異常了,try中有return語句,catch中有
* return語句等情況。
* 3.像數據庫連接、輸入輸出流、網絡編程Socket等資源,JVM是不能自動的回收的,我們需要自己手動的進行資源的
* 釋放。此時的資源釋放,就需要聲明在finally中。
*
*/
public class FinallyTest {
@Test
public void test2() {
FileInputStream fis = null;
try {
File file = new File("hello1.txt");
fis = new FileInputStream(file);
int data = fis.read();
while (data != -1) {
System.out.print((char) data);
data = fis.read();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fis != null)
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Test
public void testMethod() {
int num = method();
System.out.println(num);
}
public int method() {
try {
int[] arr = new int[10];
System.out.println(arr[10]);
return 1;
} catch (ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
return 2;
} finally {
System.out.println("我一定會被執行");
return 3;
}
}
@Test
public void test1() {
try {
int a = 10;
int b = 0;
System.out.println(a / b);
} catch (ArithmeticException e) {
// e.printStackTrace();
int[] arr = new int[10];
System.out.println(arr[10]);
} catch (Exception e) {
e.printStackTrace();
}
// System.out.println("我好慢呀~~~");
finally {
System.out.println("我好慢呀~~~");
}
}
}
- hello.txt
hello,java!
異常處理機制二:throws
-
聲明拋出異常是Java中處理異常的第二種方式
- 如果一個方法(中的語句執行時)可能生成某種異常,但是並不能確定如何處理這種異常,則此方法應顯示地聲明拋出異常,表明該方法將不對這些異常進行處理,而由該方法的調用者負責處理。
-
在方法聲明中用throws語句可以聲明拋出異常的列表,throws後面的異常類型可以是方法中產生的異常類型,也可以是它的父類。
/*
* 異常處理的方式二:throws + 異常類型
*
* 1. "throws + 異常類型"寫在方法的聲明處。指明此方法執行時,可能會拋出的異常類型。
* 一旦當方法體執行時,出現異常,仍會在異常代碼處生成一個異常類的對象,此對象滿足throws後異常
* 類型時,就會被拋出。異常代碼後續的代碼,就不再執行!
*
* 關於異常對象的產生:① 系統自動生成的異常對象
* ② 手動生成一個異常對象,並拋出(throw)
*
* 2. 體會:try-catch-finally:真正的將異常給處理掉了。
* throws的方式只是將異常拋給了方法的調用者。 並沒有真正將異常處理掉。
*
*/
public class ExceptionTest2 {
public static void main(String[] args){
try {
method2();
} catch (IOException e) {
e.printStackTrace();
}
method3();
}
public static void method3(){
try {
method2();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void method2() throws IOException{
method1();
}
public static void method1() throws FileNotFoundException,IOException{
File file = new File("hello1.txt");
FileInputStream fis = new FileInputStream(file);
int data = fis.read();
while(data != -1){
System.out.print((char)data);
data = fis.read();
}
fis.close();
System.out.println("hahaha!");
}
}
重寫方法聲明拋出異常的原則
import java.io.FileNotFoundException;
import java.io.IOException;
/*
* 方法重寫的規則之一:
* 子類重寫的方法拋出的異常類型不大於父類被重寫的方法拋出的異常類型
*
*/
public class OverrideTest {
public static void main(String[] args) {
OverrideTest test = new OverrideTest();
test.display(new SubClass());
}
public void display(SuperClass s){
try {
s.method();
} catch (IOException e) {
e.printStackTrace();
}
}
}
class SuperClass{
public void method() throws IOException{
}
}
class SubClass extends SuperClass{
public void method()throws FileNotFoundException{
}
}
/* 3. 開發中如何選擇使用try-catch-finally 還是使用throws?
* 3.1 如果父類中被重寫的方法沒有throws方式處理異常,則子類重寫的方法也不能使用throws,意味着如果
* 子類重寫的方法中有異常,必須使用try-catch-finally方式處理。
* 3.2 執行的方法a中,先後又調用了另外的幾個方法,這幾個方法是遞進關係執行的。我們建議這幾個方法使用throws
* 的方式進行處理。而執行的方法a可以考慮使用try-catch-finally方式進行處理。
*/
手動拋出異常
-
Java異常類對象除在程序執行過程中出現異常時由系統自動生成並拋出,也可根據需要使用人工創建並拋出。
-
首先要生成異常類對象,然後通過throw語句實現拋出操作(提交給Java運行環境)
-
可以拋出的異常必須是Throwable或其子類的實例。下面的語句在編譯時將會產生語法錯誤:
-
public class StudentTest {
public static void main(String[] args) {
try {
Student s = new Student();
// s.regist(1001);
s.regist(-1001);
System.out.println(s);
} catch (Exception e) {
// e.printStackTrace();
System.out.println(e.getMessage());
}
}
}
class Student{
private int id;
public void regist(int id) throws Exception{
if(id > 0){
this.id = id;
}else{
// System.out.println("您輸入的數據非法!");
//手動拋出異常
// throw new RuntimeException("您輸入的數據非法!");
throw new Exception("您輸入的數據非法!");
}
}
@Override
public String toString() {
return "Student [id=" + id + "]";
}
}
用戶自定義異常類
- 一般地,用戶自定義異常類都是RuntimeException的子類。
- 自定義異常類通常需要編寫幾個重載的構造器。
- 自定義異常需要提供serialVersionUID
- 自定義的異常通過throw拋出。
- 自定義異常最重要的是異常類的名字,當異常出現時,可以根據名字判斷異常類型。
/*
* 如何自定義異常類?
* 1.繼承於現有的異常結構:RuntimeException 、Exception
* 2.提供全局常量:serialVersionUID
* 3.提供重載的構造器
*
*/
public class MyException extends RuntimeException{
static final long serialVersionUID = -7034897193246939L;
public MyException(){
}
public MyException(String msg){
super(msg);
}
}
練習
- 練習1——ReturnExceptionDemo類
public class ReturnExceptionDemo {
static void methodA() {
try {
System.out.println("進入方法A");
throw new RuntimeException("製造異常");
} finally {
System.out.println("用A方法的finally");
}
}
static void methodB() {
try {
System.out.println("進入方法B");
return;
} finally {
System.out.println("調用B方法的finally");
}
}
public static void main(String[] args) {
try {
methodA();
} catch (Exception e) {
System.out.println(e.getMessage());
}
methodB();
}
}
- 練習2
/*
* 編寫應用程序EcmDef.java,接收命令行的兩個參數,
* 要求不能輸入負數,計算兩數相除。
* 對 數 據 類 型 不 一 致(NumberFormatException)、
* 缺 少 命 令 行 參 數(ArrayIndexOutOfBoundsException、
* 除0(ArithmeticException)及輸入負數(EcDef自定義的異常)進行異常處理。
*
* 提示:
* (1)在主類(EcmDef)中定義異常方法(ecm)完成兩數相除功能。
* (2)在main()方法中使用異常處理語句進行異常處理。
* (3)在程序中,自定義對應輸入負數的異常類(EcDef)。
* (4)運行時接受參數java EcmDef2010//args[0]=“20”args[1]=“10”
* (5)Interger類的static方法parseInt(Strings)將s轉換成對應的int值。
* 如:int a=Interger.parseInt(“314”);//a=314;
*/
public class EcmDef {
public static void main(String[] args) {
try {
int i = Integer.parseInt(args[0]);
int j = Integer.parseInt(args[0]);
int result = ecm(i,j);
System.out.println(result);
} catch (NumberFormatException e) {
System.out.println("數據類型不一致");
}catch (ArrayIndexOutOfBoundsException e){
System.out.println("缺少命令行參數");
}catch (ArithmeticException e){
System.out.println("除0");
}catch (EcDef e) {
System.out.println(e.getMessage());
}
}
public static int ecm(int i, int j) throws EcDef{
if(i < 0 || j < 0){
throw new EcDef("分子或分母爲負數了!");
}
return i / j;
}
}
- 練習2的自定義異常類——EcDef
//自定義異常類
public class EcDef extends Exception {
static final long serialVersionUID = -33875164229948L;
public EcDef() {
}
public EcDef(String msg) {
super(msg);
}
}
異常總結
總結:異常處理5個關鍵字
- 世界上最遙遠的距離,是我在if裏你在else裏,似乎一直相伴又永遠分離;
- 世界上最癡心的等待,是我當case你是switch,或許永遠都選不上自己;
- 世界上最真情的相依,是你在try我在catch。無論你發神馬脾氣,我都默默承受,靜靜處理。到那時,再來期待我們的finally。
整個Java全棧系列都是筆者自己敲的筆記。寫作不易,如果可以,點個讚唄!✌