什麼是異常?讓我們先運行一段代碼來理解
class Demo
{
int chuFa(int x,int y)//這是一個除法運算
{
return x/y;
}
}
public class ExceptionDemo {
public static void main(String[] args)
{
Demo d = new Demo();
int a = d.chuFa(4,1);//這裏傳入的除數是1
System.out.println(a);
System.out.println("over");
}
}
運行結果如下:
而當我把主函數中的除數換成0的話,我們知道在數學中這是沒有意義的運算。在Java運行時也會運行失敗,提示如下:
結果表明,在程序運行的過程中出現了不正常的情況,
是什麼情況不正常呢?提示中給出,ArithmeticException:/ by zero 算術異常:被0除了
我們把這樣在程序中出現的不正常的情況稱之爲異常。
異常:就是程序在運行時出現的不正常情況。
異常由來:問題現實生活中一個具體的事物,也可以通過Java類的形式進行描述,並封裝成對象。其實就是Java對不正常情況進行描述後的對象體現。(簡單說就是把問題封裝成了對象)(這裏又體現了Java語言的封裝性和麪向對象的特徵)
對於問題的劃分,有兩種,一種是嚴重的問題,一種是非嚴重的問題
對應於嚴重的,Java通過Error類進行描述
對於非嚴重的,Java通過Exception類進行描述
對於Exception,一般都會編寫針對性的代碼進行處理
無論Error還是Exception都具有一些共性的內容。
比如:不正常情況的信息,引發原因等。
所以,Error和Exception都有一個父類叫做Throwable
異常最常見的處理方法如下:
try
{
需要被檢測的代碼
}
catch (異常類 變量)
{
異常的處理方式
}
finally
{
一定會執行的語句
}
try catch方法處理異常的流程
當出現異常情況後,會創建一個異常對象,這個異常情況被try檢測到以後就會拋給catch,catch會做出對應的解決方式,處理完異常以後,之後的代碼還可以運行到。把之前的代碼加上異常處理機制後就是:
public class ExceptionDemo {
public static void main(String[] args)
{
Demo d = new Demo();
try
{
int a = d.chuFa(4,0);//這裏傳入的除數是1
System.out.println(a);
}
catch (Exception e)
{
System.out.println("除零啦");
}
System.out.println("over");
}
}
運行結果就變成了:
從結果中可以看出,異常處理完之後,會繼續執行下邊的語句
對於異常常見的幾種獲取信息的方法:
String getMessage();//引發錯誤的原因
String toString();//異常類+引發錯誤的原因
void printStackTrace();//異常類+引發錯誤的原因+追蹤錯誤的位置信息
聲明異常用throws關鍵字
由於在調用方法的時候會有異常發生,導致程序停止,所以在定義方法的時候要用throws關鍵字聲明一下會有異常情況,意思就是告訴調用該方法的人,這個方法可能會發生異常,你需要處理
作用:提高安全性,讓調用和作出處理,不處理則編譯失敗。
對於調用了已聲明瞭異常的方法處理方式
一旦調用了已經聲明異常的方法,就必須對其作出處理,否則會編譯失敗
處理方式有兩種:
throws :拋出,由拋出函數的外層函數進行處理,如果處理不了,再次聲明拋出可以繼續往外拋,那麼,拋到哪裏是個頭呢?
jvm虛擬機,虛擬機接收到拋出的異常之後,會使用默認的方法,讓程序停掉,
try:
多異常的處理方式
- 聲明異常時,建議聲明更爲具體的異常,這樣處理的可以更具體,
- 對方聲明幾個異常,就 對應有幾個catch塊。不要定義多餘的catc塊。
如果多個catch塊中的異常出現繼承關係,父類異常catch塊放在最下邊,如果父類異常catch塊放在了最上邊,那麼虛擬機認爲後邊的全是廢話,編譯失敗。
舉例如下:
class Demo
{
int chuFa(int x,int y) throws ArithmeticException,ArrayIndexOutOfBoundsException //在這個方法裏聲明瞭兩個異常
{
int[] arr = new int[x];
System.out.println(arr[4]);//如果數組角標越界的話會觸發角標越界異常
return x/y;
}
}
public class ExceptionDemo2 {
public static void main(String[] args)
{
Demo d = new Demo();
try{
int a = d.chuFa(5,1);//這裏傳入參數容易引發異常
System.out.println(a);
}
catch(ArithmeticException e)//觸發ArithmeticException異常的時候執行的代碼塊
{
System.out.println("除零了");
}
catch(ArrayIndexOutOfBoundsException e)//觸發ArrayIndexOutOfBoundsException異常的時候執行的代碼塊
{
System.out.println("角標越界啦");
}
catch(Exception e)//這是父類異常的代碼塊
{
System.out.println("hahaha:"+e.toString());
}
System.out.println("over");
}
}
這個時候沒有發生任何異常,運行會正常結束:
當把chuFa方法中的參數改爲4,1時(創建一個角標越界的異常)結果如下:
當把參數改爲5,0時,(創建一個運算異常) 結果如下:
我們可以看到,放在最下邊的catch塊,雖然是每個異常的父類,但並沒有執行,
因爲代碼是從上到下執行的,找到了符合條件的catch塊,就不會去找其他的catch塊,這樣寫父類的異常catch塊是爲了避免其他異常蹦出來中斷程序,可以編譯通過,也可以運行但是不建議這麼寫,因爲在運行過程中出了啥異常都會被父類異常處理掉,如果出現了別的異常,程序員需要知道;
建議:在進行異常處理時,catch中一定要有具體的定義方式,不要簡單的定義一句打印異常信息,也不要簡單的書寫一條輸出語句
自定義異常
在後期做開發的時候,描述具體的事物有具體的要求,也就是說會遇到不同的異常,而這些是Java沒有進行過描述並封裝對象的
所以,對於這些特有的問題,我們可以按照Java的封裝思想,將特有的問題進行自定義的異常封裝。
怎麼樣進行異常的封裝呢?我們用代碼來體現:
//需求:定義一個除法運算,除了不能除零外,還要定義不能除以負數
class FuShuException extends Exception//定義一個異常類(對象),需要繼承Exception
{
private int value;//想把出錯誤的數字也獲取到,在這裏創建一個value
FuShuException(String msg,int b)//因爲自定義異常還沒有定義自己的異常信息,所以在初始化的時候就要傳一個描述性的文字,作爲他的異常信息描述語句
{
super(msg);
//Exception類中已經定義了自定義異常信息的方法,這裏把參數傳給父類Exception就行了
//下邊調用toString方法的時候會自動調用getMessage方法,將描述語句打印出來
this.value = b;
}
public int getValue()
{
return value;
}
}
class Demo
{
int chuFa(int x,int y) throws FuShuException,ArithmeticException
{
if (y<0)//除數小於0,創建異常對象並拋出
{
throw new FuShuException("除數爲負數了 / by fushu",y);
}
return x/y;
}
}
public class ExceptionDemo3 {
public static void main(String[] args)
{
Demo d = new Demo();
try//對自定義的異常進行處理
{
int a = d.chuFa(4,-1);
System.out.println(a);
}
catch (FuShuException e)
{
System.out.println(e.toString()+"---錯誤的數字是"+e.getValue());
}
catch(ArithmeticException e)
{
System.out.println(e.toString());
}
}
}
運行結果爲:
我們可以看到,自定義異常,必須是自定義類繼承Exception
繼承Exception 的原因:
異常體系中有一個特點,因爲異常類和對象都被拋出
他們都具備可拋性。這個可拋性是Throwable這個體系中的獨有特點
注意的是,在自定義異常的時候,沒有必要所有的信息都自己去做,
比如異常信息的操作,在父類中就已經做完了,父類中有這樣的方法:
而在Exception類中,也有相同的方法:
所以我們這裏只要把自定義的信息用super語句傳給父類。
就是以上代碼中的 “super(msg);”
就可以直接通過getMessage方法獲取自定義的異常信息。
throw和throws的區別
throws使用在函數上,throw使用在函數內
throws後邊跟的是異常類,可以跟多個,用逗號隔開
throw後跟的是異常對象
運行時異常
Exception中有一個特殊的子類異常RuntimeException
如果在函數中拋出該異常,函數上可以不用聲明,編譯一樣通過。
如果在函數上聲明瞭該異常,調用者可以不用進行處理,編譯一樣通過。
之所以不用在函數聲明,是因爲不需要讓調用者處理
當該異常發生,希望程序停止,因爲在運行時,出現了無法繼續運算的情況,希望程序停止後,對代碼進行修正。
子類例如:數組角標越界異常(角標越界都找不到元素,沒有辦法繼續運算下去了),空指針異常(對象沒有指向的示例化對象,也無法進行下一步的運算)
這些情況下,我們希望程序停下來,不希望catch處理之後繼續再進行運算,
因爲再運算也沒有什麼意義了。
用一段小代碼來具體體現一下RunTimeException的特點:
//運行時異常:
//需求:定義一個除法運算,除了不能除零外,還要定義不能除以負數
//一旦發生除零,除負數的情況,應該讓程序停下來,因爲繼續算也沒什麼 意義了
class FuShuException extends RuntimeException//該種異常我們不會處理,發生該異常的時候希望程序停下來,所以這裏要繼承RunTimeException
{
FuShuException(String msg)
{
super(msg);
}
}
class Demo
{
int chuFa(int x,int y)//因爲裏邊拋出的是RunTimeException,所以在這裏可以不用聲明,在主函數中也不用處理
{
if (y<0)
{
throw new FuShuException("除數爲負數了 / by fushu");
}
if (y==0)
{
throw new ArithmeticException("除數爲0,沒意義");
}
return x/y;
}
}
public class ExceptionDemo4 {
public static void main(String[] args)
{
Demo d = new Demo();
int a = d.chuFa(4,0);
System.out.println(a);
}
}
運行結果爲下圖:
在自定義異常的時候:如果該異常的發生無法再繼續進行運算,就讓自定義異常繼承RunTimeException。
對於異常分爲兩種:
1,編譯時被檢測的異常。
2,編譯時不被檢測的異常(運行時異常,RunTimeException以及其子類)
所以在分析問題定義異常的時候,先要分析該異常能不能被處理,
|—–如果不能處理,需要修正代碼的時候,就繼承RunTimeException,
|—–如果該異常可以被try catch處理,就繼承Exception。
一個小練習:老師帶電腦上課的過程中異常處理:
//異常練習
/*
需求:畢老師講課,要用電腦講課,
而會時常有異常,比如:電腦藍屏了,:處理方式,重啓,繼續上課
電腦冒煙了。:處理方式,又有了一個新的異常:
課時無法完成,處理方式:換老師或者放假
思想:封裝,將各種異常封裝成對象,關鍵字提取;
*/
class LanPingException extends Exception//藍屏異常
{
LanPingException(String msg)
{
super(msg);
}
}
class MaoYanException extends Exception//冒煙異常
{
MaoYanException(String msg)
{
super(msg);
}
}
class NoPlanException extends Exception//課時無法繼續異常
{
NoPlanException(String msg)
{
super(msg);
}
}
class Teacher
{
private String name;
Teacher(String name)
{
this.name = name;
}
public void jiangKe() throws NoPlanException//老師講課過程中會拋出課時無法繼續異常,所以在這裏標識一下
{
//老師來講課,帶上了自己的電腦,開啓電腦之後開始講課
Computer cmpt = new Computer();
try
{
cmpt.run();//而電腦會出現異常,所以在這裏調用run方法的時候要對異常進行處理
}
catch (LanPingException e)
{
cmpt.restart();
}
catch (MaoYanException e)
{
//發生冒煙異常的時候,處理方法應該是先讓學生們做練習,然後給調用講課方法的人拋一個課時無法完成異常
//交給上一層領導處理
test();
throw new NoPlanException("課時無法繼續");
}
System.out.println("開始講課");
}
public void test()
{
System.out.println("做練習");
}
}
class Computer
{
private int state = 3;
public void run() throws MaoYanException,LanPingException
{
//電腦的諸多自定義異常都要在這裏產生異常對象
if(state==2)
{
throw new LanPingException("藍屏了");
}
if (state==3)
{
throw new MaoYanException("冒煙了");
}
System.out.println("電腦運行");
}
public void restart()
{
System.out.println("電腦重啓");
}
}
public class ExceptionTest{
public static void main(String[] args)
{
Teacher t = new Teacher("畢老師");
try
{
t.jiangKe();//在調用講課方法的時候,接收到課時無法完成異常,將在這裏進行處理
}
catch (NoPlanException e)
{
System.out.println("換老師或者放假-----"+e.getMessage());
}
}
}
運行結果如下:
當代碼中state爲1的時候(電腦沒有發生任何異常的時候)結果爲:
當代碼中state爲2的時候(電腦發生藍屏異常的時候)結果爲:
當代碼中state爲3的時候(電腦發生冒煙異常的時候)結果爲:
異常——finally
finally代碼塊,定義一定會執行的代碼,通常用於關閉資源
在數據庫操作中最爲常見的例子:
public void method()
{
連接數據庫;
數據操作//throw new SQLException如果拋出異常,下面的代碼就不會運行了,也就是說資源沒有被關閉,
關閉數據庫//而這一步是必須要執行的
}
該代碼中如果異常出現,則不會關閉資源,這不是我們想要的。
解決這樣的問題可以這麼做:
public void method() throws 沒有操作成功異常
{
try
{
連接數據庫;
數據操作;//throw new 數據操作異常;
}
catch(數據操作異常 e)
{
會對數據庫異常進行處理;
throw new 沒有操作成功異常;
}
finally
{
關閉數據庫;
}
}
在Java中,數據存儲操作是調用數據庫的方法完成的,
我要存數數據,把數據給你即可,至於你是怎麼存的,我沒必要知道,
但是我必須知道的是:數據有沒有存儲成功,
就像在以上的代碼中,數據庫操作出現異常,應該在數據庫內部直接處理,
而不是拋出來異常,因爲拋出來我也看不懂,所以在你內部直接處理掉就可以了。
但是你要告訴我,有沒有存儲成功,也就是說這裏可以拋出來一個“沒有操作成功異常”這個我可以處理。
而不管有沒有存儲成功,資源是必須要關閉的,所以我們在這裏把關閉資源的動作放在finally語句中。
異常處理語句的其他格式:
//第一種格式:
try
{
}
catch()
{
}
//第二種格式:
try
{
}
caatch()
{
}
finally
{
}
//第三種格式
try
{
}
finally
{
}
記住一點:catch是用來處理異常的,如果沒有catch代表該異常沒有被處理過,如果該異常時檢測時異常,那麼必須申明。
異常在子父類覆蓋中的體現
- 子類在覆蓋父類時,如果父類的方法拋出異常,那麼子類的覆蓋方法只能拋出父類的異常,或者父類異常的子類,
- 如果父類方法拋出多個異常,那麼子類在覆蓋該方法時,只能拋出父類異常的子集。
- 如果父類或者接口的方法中沒有異常拋出,那麼子類在覆蓋方法時,也不可以拋出異常。如果子類方法拋出了異常嗎,就必須要進行try處理,絕對不能拋。
-
用一個代碼練習來說明這段文字:
//異常之間的關係是:
// |--Exception
// |--AException
// |--BException
// |--CException
class AException extends Exception
{
}
class BException extends AException
{
}
class CException extends Exception
{
}
class Fu
{
public void show() throws AException//父類方法拋出一個AException
{
}
}
class Zi extends Fu//子類繼承父類
{
public void show()throws BException//在這裏拋出一個AException(也可以拋出BException,但是不能拋出CException)
//如果這裏真的會冒出一個C異常的話,只能try,絕對不能拋
{
}
}
class Test1
{
public void function(Fu f)
{
try
{
f.show();
}
catch (AException e)
{
}
}
}
public class Test {
public static void main(String[] args)
{
Test1 t1 = new Test1();
t1.function(new Zi());
}
}
通過一串小代碼來更加熟練的運用一下異常的處理機制:
//長方形圓形的面積計算程序
/*
首先思考:長方形,圓形都是圖形,都有獲取面積的這樣一個功能,
但是長和寬是長方形特有的,半徑是圓特有的屬性
可以將獲取面積的方法封裝在一個接口中
*/
interface Shape
{
void getArea();
}
//長方形的計算過程:
class ChangFangXing implements Shape
{
private int chang;
private int kuang;
ChangFangXing(int chang,int kuang)
{
this.chang = chang;
this.kuang = kuang;
}
public void getArea()
{
//如果給出的值小於0的話,必須停掉程序,不可以再往下運行了。
if (chang<=0 || kuang<=0)
{
throw new NoValueException("長或寬的值小於0!!!");
}
System.out.println(chang*kuang);
}
}
//圓的面積計算過程:
class Yuan implements Shape
{
private int radiu;
//PI的值是不變的,所以在這裏把他定義成常量
public static final double PI = 3.14;
Yuan(int radiu)
{
this.radiu = radiu;
}
public void getArea()
{
//同樣的,如果他的半徑小於0,程序停掉
if (radiu<=0)
{
throw new NoRadiuException("圓的半徑值出錯!!!");
}
System.out.println(radiu*radiu*PI);
}
}
class NoValueException extends RuntimeException
{
NoValueException(String msg)
{
super(msg);
}
}
class NoRadiuException extends RuntimeException
{
NoRadiuException(String msg)
{
super(msg);
}
}
public class ExceptionTest2 {
public static void main(String[] args)
{
//創建一個長方形對象,求面積
ChangFangXing cfx = new ChangFangXing(3,4);
cfx.getArea();
//創建一個圓的對象,並求面積
Yuan y = new Yuan(2);
y.getArea();
}
}
運算結果爲:
異常知識總結:
- 異常是什麼?
- 是對問題的描述,並將問題對象封裝成對象
- 異常體系:
- Throwable
- Error
- Exception
- RuntimeException
- Throwable
- 異常體系的特點:異常體系中所有類以及建立的對象都具有可拋性。也就是說可以被throw和throws關鍵字操作,只有異常體系具備這個特點。
- throw和throws的用法:
- throw定義在函數內,用於拋出異常對象
- throws定義在函數上,用於拋出異常類,可以拋出多個,用逗號隔開。
- 當函數內容有throw拋出異常對象,並未進行try處理,必須要在函數上聲明,否則編譯失敗
- 注意:RunTimeException除外,也就是說,函數中如果拋出RunTimeException函數上可以不用聲明
- 如果函數上聲明瞭異常,調用者需要進行處理。處理方法可以是try和throws
- 異常有兩種:
- 編譯時被檢測異常
- 該異常在編譯時,如果沒有進行過處理(沒有拋也沒有try),編譯失敗
- 該異常被標識,代表着可以被處理
- 運行時異常(編譯時不檢測)
- 在編譯時,不需要處理,編譯器不檢查。
- 該異常發射給你時,建議不處理,讓程序停止,需要對代碼進行修正
- 編譯時被檢測異常
- 異常處理語句:
try
{
需要被檢測的代碼;
}
catch()
{
處理異常的代碼;
}
finally
{
一定會執行的代碼;
}
有三個結合方式:
1.try catch
2.try finally
3.try catch finally - 注意:
- finally中通常是關閉資源的代碼,因爲資源必須釋放
- finally只有一種情況不會執行,當執行到System.exit(0);finally不會執行,因爲jvm結束運行了
- 自定義異常:
- 定義類繼承Exception或者RuntimeException
- 1.爲了讓該自定義類具備可拋性。
- 2.讓該類具備操作異常的共性方法
- 3.當要定義自定義異常信息時,可以使用父類已經定義好的功能。將異常信息傳遞給父類的構造函數
class MyException extends Exception
{
MyException(String message)
{
super(message);
}
}
- 自定義異常:按照Java面向對象的思想,將程序中出現的特有問題進行封裝
- 異常的好處:
- 將問題進行封裝。
- 將正常流程代碼和問題處理代碼相分離,方便於閱讀。
- 異常的處理原則:
- 1,處理方式有兩種:try 或者throws
- 2,調用到拋出異常的功能時,拋出幾個,就處理幾個。
- 一個try對應多個catch
- 3,多個catch,父類的catch放到最下邊
- 4,catch內,不需要定義針對性的處理方式,不要簡單的定義printstrackTrace,也不要不寫
- 當捕獲到的異常,本功能處理不了時,可以繼續在catch中拋出。
try
{
throw new AException();
}
catch(AException e)
{
throw e
}
如果該異常處理不了,但並不屬於該功能出現的異常。
可以將異常轉換後,再拋出和該功能相關的異常。
或者異常可以處理,但需要將異常產生的和本功能相關的問題提供出來
讓調用者知道,並處理,也可以將捕獲異常處理後,轉換新的異常拋出
try
{
throw new AException();
}
catch(AException e )
{
//對AException處理。
throw new BException();
}
比如:匯款的例子。
我去給張三匯款,但是出了異常沒有匯成功,匯款機需要先將我匯的錢再存進我的卡里,然後告訴我,沒有匯成功,我再去處理這個沒有匯成功的異常(換個銀行或者換臺機子)
- 異常的注意事項:
- 在子父類覆蓋時:
- 1,子類拋出的異常必須是父類的異常或者異常的子類,或者子集
- 2,如果父類或者接口沒有異常拋出時,子類覆蓋出現異常,只能try不能throws
參閱例子:
老師用電腦上課
圖形面積