【java基礎之異常】死了都要try,不淋漓盡致地catch我不痛快!

@[toc]javascript

一、異常

1.1 異常概念

異常 :簡單說就是不正常運行,最終致使JVM的非正常中止。java

在Java等面向對象的編程語言中,異常自己是一個類,產生異常就是建立異常對象並拋出了一個異常對象。Java處理異常的方式是中斷處理。程序員

異常指的並非語法錯誤,語法錯了,編譯不經過,不會產生字節碼文件,根本不能運行.數據庫

1.2 異常體系

異常機制實際上是幫助咱們找到程序中的問題,異常的根類是java.lang.Throwable,其下有兩個子類:java.lang.Errorjava.lang.Exception,日常所說的異常指java.lang.Exception編程

\[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-A0NGTCB9-1570323592244)(img\異常體系.png)\]

Throwable中的經常使用方法:數組

一、 public void printStackTrace():打印異常的詳細信息。安全

包含了異常的類型,異常的緣由,還包括異常出現的位置,在開發和調試階段,都得使用printStackTrace。網絡

二、 public String getMessage():獲取發生異常的緣由。編程語言

提示給用戶的時候,就提示錯誤緣由。工具

三、 public String toString():獲取異常的類型和異常描述信息(不用)。

出現異常,最簡單的方式就是把異常的簡單類名,拷貝到API中去查。

在這裏插入圖片描述

1.3 異常分類

咱們日常說的異常就是指Exception,由於這類異常一旦出現,咱們就要對代碼進行更正,修復程序。

異常(Exception)的分類:根據在編譯時期仍是運行時期去檢查異常?

  • 編譯時期異常:checked異常。在編譯時期,就會檢查,若是沒有處理異常,則編譯失敗。(如日期格式化異常)
  • 運行時期異常:runtime異常。在運行時期,檢查異常.在編譯時期,運行異常不會編譯器檢測(不報錯)。(如數學異常)
    在這裏插入圖片描述
    ​    \[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-yWbBNQxN-1570323592247)(img\異常的分類.png)\]

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異常處理的五個關鍵字:trycatchfinallythrowthrows

2.1 拋出異常throw

在編寫程序時,做爲一個優秀的程序員必需要考慮程序出現問題的狀況。舉個栗子,在定義方法時,方法須要接受參數。那麼,當調用方法使用接受到的參數時,首先須要先對參數數據進行合法的判斷,數據若不合法,就應該告訴調用者,傳遞合法的數據進來。這時須要使用拋出異常的方式來告訴調用者。這個時候throw就派上用場了!

在java中,提供了一個throw關鍵字,Throw用來拋出一個指定的異常對象。從而能夠

一、建立一個異常對象。封裝一些提示信息(信息能夠本身編寫)。

二、經過關鍵字throw就能夠將這個異常對象告知給調用者,還能夠將這個異常對象傳遞到調用者處。

throw小結

throw用在方法內,後面接一個異常對象,使用格式爲throw new 異常類名(參數);,將這個異常對象傳遞到調用者處,並結束當前方法的執行。

throw使用的格式以下

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運行效果分析:

在這裏插入圖片描述
再來看看不使用throw的實例使用的運行

public static void main(String[] args) {
        //建立一個數組
        int[] arr = {2,4,52,2};
        //根據索引找對應的元素
        int index = 4; //注意索引4已經越界了
        int ele=arr[index];

        System.out.println(ele);
        System.out.println("over");
}
複製代碼

運行結果

在這裏插入圖片描述
這樣一對比 throw的優點就不言而喻了~

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聲明處理。

聲明異常:將問題標識出來,報告給調用者。若是方法內經過throw拋出了編譯時異常,而沒有捕獲處理(稍後講解該方式),那麼必須經過throws進行聲明,讓調用者去處理。

throws小結

關鍵字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();
        }
    }
}
複製代碼

throw 和 throws 的區別小結

throw:

一、表示方法內拋出某種異常對象 二、若是異常對象是非 RuntimeException 則須要在方法申明時加上該異常的拋出 即須要加上 throws 語句 或者 在方法體內 try catch 處理該異常,不然編譯報錯 三、執行到 throw 語句則後面的語句塊再也不執行

throws:

一、方法的定義上使用 throws 表示這個方法可能拋出某種異常 二、須要由方法的調用者進行異常處理

2.4 捕獲異常try…catch

若是異常出現的話,會馬上終止程序,因此咱們得處理異常:

  1. 該方法不處理,而是聲明拋出,由該方法的調用者來處理(throws)。
  2. 在方法中使用try-catch的語句塊來處理異常。

try-catch的方式就是捕獲異常。

捕獲異常:Java中對異常有針對性的語句進行捕獲,能夠對出現的異常進行指定方式的處理。

捕獲異常語法以下:

try{
     編寫可能會出現異常的代碼
}catch(異常類型  e){
     處理異常的代碼
     //記錄日誌/打印異常信息/繼續拋出異常
}
複製代碼

try:該代碼塊中編寫可能產生異常的代碼。

catch:用來進行某種異常的捕獲,實現對捕獲到的異常進行處理。

注意:trycatch都不能單獨使用,必須連用。

演示以下:

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("文件不存在");
        }
    }
}
複製代碼

當只有在try或者catch中調用退出JVM的相關方法,此時finally纔不會執行,不然finally永遠會執行。

2.5 finally 代碼塊與return語句

還有個特別重要的點就是在try塊或catch塊中遇到return語句時,finally語句塊將在方法返回以前被執行,另外finally語句中也能夠有return語句,可是儘可能避免有return語句(會報警告)

2.6 異常注意事項

多個異常使用捕獲又該如何處理呢?

  1. 多個異常分別處理。

  2. 多個異常一次捕獲,屢次處理。

  3. 多個異常一次捕獲一次處理。

通常咱們是使用一次捕獲屢次處理方式,格式以下:

try{
     編寫可能會出現異常的代碼
}catch(異常類型A  e){  當try中出現A類型異常,就用該catch來捕獲.
     處理異常的代碼
     //記錄日誌/打印異常信息/繼續拋出異常
}catch(異常類型B  e){  當try中出現B類型異常,就用該catch來捕獲.
     處理異常的代碼
     //記錄日誌/打印異常信息/繼續拋出異常
}
複製代碼

注意:這種異常處理方式,要求多個catch中的異常不能相同,而且若catch中的多個異常之間有子父類異常的關係,那麼子類異常要求在上面的catch處理,父類異常在下面的catch處理。

異常注意小結

  • 運行時異常被拋出能夠不處理。即不捕獲也不聲明拋出。

  • 若是finallyreturn語句,永遠返回finally中的結果,避免該狀況. 上面也提到過!

  • 若是父類拋出了多個異常,子類重寫父類方法時,拋出和父類相同的異常或者是父類異常的子類或者不拋出異常。

  • 父類方法沒有拋出異常,子類重寫父類該方法時也不可拋出異常。此時子類產生該異常,只能捕獲處理,不能聲明拋出

三、自定義異常

3.1 概述

爲何須要自定義異常類:

Java中不一樣的異常類,分別表示着某一種具體的異常狀況,在開發中老是有些異常狀況是SUN沒有定義好的,此時咱們根據本身業務的異常狀況來定義異常類。例如程序員頭髮數爲負數、小明考試成績負數問題等等。

在以前程序代碼中,發現這些異常都是JDK內部定義好的,可是實際開發中也會出現不少異常,這些異常極可能在JDK中沒有定義過,例如程序員老婆數量爲負數、程序員老王智商爲負數問題.那麼能不能本身定義異常呢?答案是能滴!

什麼是自定義異常類:

在開發中根據本身業務的異常狀況來定義異常類.

自定義一個業務邏輯異常: RegisterException。一個註冊異常類。

異常類如何定義:

  1. 自定義一個編譯期異常: 自定義類 並繼承於java.lang.Exception
  2. 自定義一個運行時期的異常類:自定義類 並繼承於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 RegisterException {
        for (String name : names) {
            if(name.equals(uname)){//若是名字在這裏面 就拋出登錄異常
                throw new RegisterException("親"+name+"已經被註冊了!");
            }
        }
        return true;
    }
}
複製代碼

用戶名不存在,登陸成功,效果分析以下:

在這裏插入圖片描述
用戶名存在,登陸失敗,效果分析以下:
在這裏插入圖片描述

四、來!和異常大幹一場

本篇文章到這裏基本就看完了,博主我也不知道各位吸取了多少,我得對各位們負責鴨,不能講完就完事了,因而仍是以爲讓各位鞏固鞏固一下比較好,若是各位可以作出下面這道典型的異常題,說明異常方面的知識對你來講就沒多大問題了,若是以爲哪裏還不是特別懂,能夠及時告訴我,凡是看到了都會第一時間回覆。好了,試想一下下面程序運行結果是啥~

package com.gx.Expetion;

public class TestException {
	public TestException() {
	}
	boolean testEx() throws Exception {
		boolean ret = true;
		try {
			ret = testEx1();
		} catch (Exception e) {
			System.out.println("testEx, catch exception");
			ret = false;
			throw e;
		} finally {
			System.out.println("testEx, finally; return value=" + ret);
			return ret;
		}
	}
 
	boolean testEx1() throws Exception {
		boolean ret = true;
		try {
			ret = testEx2();
			if (!ret) {
				return false;
			}
			System.out.println("testEx1111, at the end of try");
			return ret;
		} catch (Exception e) {
			System.out.println("testEx1111, catch exception");
			ret = false;
			throw e;
		} finally {
			System.out.println("testEx1111, finally; return value=" + ret);
			return ret;
		}
	}
 
	boolean testEx2() throws Exception {
		boolean ret = true;
		try {
			int b = 12;
			int c;
			for (int i = 2; i >= -2; i--) {
				c = b / i;
				System.out.println("i=" + i);
			}
			return true;
		} catch (Exception e) {
			System.out.println("testEx2222, catch exception");
			ret = false;
			throw e;
		} finally {
			System.out.println("testEx2222, finally; return value=" + ret);
			return ret;
		}
	}
 
	public static void main(String[] args) {
		TestException testException1 = new TestException();
		try {
			testException1.testEx();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
複製代碼

運行結果:

i=2
i=1
testEx2222, catch exception
testEx2222, finally; return value=false
testEx1111, finally; return value=false
testEx, finally; return value=false
複製代碼

若是以爲哪裏還不是特別懂,能夠及時告訴我,凡是看到了都會第一時間回覆!!!

到這裏,不少小白同窗依舊木有感覺到博主「死了都要try,不淋漓盡致地catch我不痛快!」的那個feel,對咩?行,博主就知足知足小白的那個feel,feelfeel一下 ~=感覺感覺一下~

/** * 把多條數據的ResultSet的結果放到 List<T>中 * @param rs ResultSet結果集 * @param obj java類的class * @return */
    public static <T> List<T> getResult(ResultSet rs, Class<T> obj) {
        try {
            List<T> list = new ArrayList<T>();
            //ResultSetMetaData 有關 ResultSet 中列的名稱和類型的信息。
            ResultSetMetaData metaData = rs.getMetaData();
            //獲取總的列數
            int count = metaData.getColumnCount();
            //遍歷ResultSet
            while (rs.next()) {
                //---建立對象實例
                T instance = obj.newInstance();
                for (int i = 1; i <= count; i++) {
                    //---獲取列名
                    String name = metaData.getColumnName(i);
                    // 改變列名格式成 java 命名格式 主要是針對 _ 分割的狀況 如user_id
                    name = toJavaField(name);
                    //---獲取類型
                    Class<?> type = obj.getDeclaredField(name).getType();
                    //---獲取setter方法
                    // 首字母大寫
                    String replace = name.substring(0, 1).toUpperCase() + name.substring(1);
                    Method setMethod = obj.getMethod("set" + replace, type);

                    //---判斷讀取數據的類型
                    if (type.isAssignableFrom(String.class)) {
                        setMethod.invoke(instance, rs.getString(i));
                    } else if (type.isAssignableFrom(int.class) || type.isAssignableFrom(Integer.class)) {
                        setMethod.invoke(instance, rs.getInt(i));
                    } else if (type.isAssignableFrom(Boolean.class) || type.isAssignableFrom(boolean.class)) {
                        setMethod.invoke(instance, rs.getBoolean(i));
                    } else if (type.isAssignableFrom(Date.class)) {
                        setMethod.invoke(instance, rs.getDate(i));
                    }
                }
                list.add(instance);
            }
            return list;

        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (InstantiationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (Exception e) {
            // TODO: handle exception
        }

        return null;
    }
複製代碼

先不說看不看得懂了,以上代碼設計反射泛型、異常~也就是try..catch~ 、String類、jdbc相關的知識,這些知識我大部分都寫過文章了,有興趣的小白同窗就直接點藍色字體進入文章。咳咳...言歸正傳,有木有發現以上代碼中使用的就是一次捕獲屢次處理方式,格式以下:

try{
     編寫可能會出現異常的代碼
}catch(異常類型A  e){  當try中出現A類型異常,就用該catch來捕獲.
     處理異常的代碼
     //記錄日誌/打印異常信息/繼續拋出異常
}catch(異常類型B  e){  當try中出現B類型異常,就用該catch來捕獲.
     處理異常的代碼
     //記錄日誌/打印異常信息/繼續拋出異常
}
複製代碼

注意:這種異常處理方式,要求多個catch中的異常不能相同,而且若catch中的多個異常之間有子父類異常的關係,那麼子類異常要求在上面的catch處理,父類異常在下面的catch處理。

這已是第二次寫到了,因此各位找到不淋漓盡致地catch不痛快的feel了咩?

最後,推薦閱讀本專欄的下兩篇java文章~有興趣的同窗能夠了解一下~

【java基礎之多態】理解多態的向上向下轉型從「媽媽我想吃烤山藥」講起

【java基礎】java繼承從「我爸是李剛」講起

歡迎各位關注個人公衆號,一塊兒探討技術,嚮往技術,追求技術...

在這裏插入圖片描述
相關文章
相關標籤/搜索