夯實(shí)Java基礎(chǔ)系列10:深入理解Java中的異常體系

目錄


- Java異常

本系列文章將整理到我在GitHub上的《Java面試指南》倉(cāng)庫(kù),更多精彩內(nèi)容請(qǐng)到我的倉(cāng)庫(kù)里查看

https://github.com/h2pl/Java-Tutorial

喜歡的話麻煩點(diǎn)下Star哈

文章首發(fā)于我的個(gè)人博客:

www.how2playlife.com

本文是微信公眾號(hào)【Java技術(shù)江湖】的《夯實(shí)Java基礎(chǔ)系列博文》其中一篇,本文部分內(nèi)容來(lái)源于網(wǎng)絡(luò),為了把本文主題講得清晰透徹,也整合了很多我認(rèn)為不錯(cuò)的技術(shù)博客內(nèi)容,引用其中了一些比較好的博客文章,如有侵權(quán),請(qǐng)聯(lián)系作者。
該系列博文會(huì)告訴你如何從入門到進(jìn)階,一步步地學(xué)習(xí)Java基礎(chǔ)知識(shí),并上手進(jìn)行實(shí)戰(zhàn),接著了解每個(gè)Java知識(shí)點(diǎn)背后的實(shí)現(xiàn)原理,更完整地了解整個(gè)Java技術(shù)體系,形成自己的知識(shí)框架。為了更好地總結(jié)和檢驗(yàn)?zāi)愕膶W(xué)習(xí)成果,本系列文章也會(huì)提供每個(gè)知識(shí)點(diǎn)對(duì)應(yīng)的面試題以及參考答案。

如果對(duì)本系列文章有什么建議,或者是有什么疑問(wèn)的話,也可以關(guān)注公眾號(hào)【Java技術(shù)江湖】聯(lián)系作者,歡迎你參與本系列博文的創(chuàng)作和修訂。

為什么要使用異常

首先我們可以明確一點(diǎn)就是異常的處理機(jī)制可以確保我們程序的健壯性,提高系統(tǒng)可用率。雖然我們不是特別喜歡看到它,但是我們不能不承認(rèn)它的地位,作用。

在沒(méi)有異常機(jī)制的時(shí)候我們是這樣處理的:通過(guò)函數(shù)的返回值來(lái)判斷是否發(fā)生了異常(這個(gè)返回值通常是已經(jīng)約定好了的),調(diào)用該函數(shù)的程序負(fù)責(zé)檢查并且分析返回值。雖然可以解決異常問(wèn)題,但是這樣做存在幾個(gè)缺陷:

1、 容易混淆。如果約定返回值為-11111時(shí)表示出現(xiàn)異常,那么當(dāng)程序最后的計(jì)算結(jié)果真的為-1111呢?

2、 代碼可讀性差。將異常處理代碼和程序代碼混淆在一起將會(huì)降低代碼的可讀性。

3、 由調(diào)用函數(shù)來(lái)分析異常,這要求程序員對(duì)庫(kù)函數(shù)有很深的了解。

在OO中提供的異常處理機(jī)制是提供代碼健壯的強(qiáng)有力的方式。使用異常機(jī)制它能夠降低錯(cuò)誤處理代碼的復(fù)雜度,如果不使用異常,那么就必須檢查特定的錯(cuò)誤,并在程序中的許多地方去處理它。

而如果使用異常,那就不必在方法調(diào)用處進(jìn)行檢查,因?yàn)楫惓C(jī)制將保證能夠捕獲這個(gè)錯(cuò)誤,并且,只需在一個(gè)地方處理錯(cuò)誤,即所謂的異常處理程序中。

這種方式不僅節(jié)約代碼,而且把“概述在正常執(zhí)行過(guò)程中做什么事”的代碼和“出了問(wèn)題怎么辦”的代碼相分離??傊?,與以前的錯(cuò)誤處理方法相比,異常機(jī)制使代碼的閱讀、編寫(xiě)和調(diào)試工作更加井井有條。(摘自《Think in java 》)。

該部分內(nèi)容選自http://www.cnblogs.com/chenssy/p/3438130.html

異?;径x

在《Think in java》中是這樣定義異常的:異常情形是指阻止當(dāng)前方法或者作用域繼續(xù)執(zhí)行的問(wèn)題。在這里一定要明確一點(diǎn):異常代碼某種程度的錯(cuò)誤,盡管Java有異常處理機(jī)制,但是我們不能以“正?!钡难酃鈦?lái)看待異常,異常處理機(jī)制的原因就是告訴你:這里可能會(huì)或者已經(jīng)產(chǎn)生了錯(cuò)誤,您的程序出現(xiàn)了不正常的情況,可能會(huì)導(dǎo)致程序失??!

那么什么時(shí)候才會(huì)出現(xiàn)異常呢?只有在你當(dāng)前的環(huán)境下程序無(wú)法正常運(yùn)行下去,也就是說(shuō)程序已經(jīng)無(wú)法來(lái)正確解決問(wèn)題了,這時(shí)它所就會(huì)從當(dāng)前環(huán)境中跳出,并拋出異常。拋出異常后,它首先會(huì)做幾件事。

首先,它會(huì)使用new創(chuàng)建一個(gè)異常對(duì)象,然后在產(chǎn)生異常的位置終止程序,并且從當(dāng)前環(huán)境中彈出對(duì)異常對(duì)象的引用,這時(shí)。異常處理機(jī)制就會(huì)接管程序,并開(kāi)始尋找一個(gè)恰當(dāng)?shù)牡胤絹?lái)繼續(xù)執(zhí)行程序,這個(gè)恰當(dāng)?shù)牡胤骄褪钱惓L幚沓绦颉?/p>

總的來(lái)說(shuō)異常處理機(jī)制就是當(dāng)程序發(fā)生異常時(shí),它強(qiáng)制終止程序運(yùn)行,記錄異常信息并將這些信息反饋給我們,由我們來(lái)確定是否處理異常。

異常體系

[外鏈圖片轉(zhuǎn)存失敗(img-KNxcBTK0-1569073569353)(https://images0.cnblogs.com/blog/381060/201311/22185952-834d92bc2bfe498f9a33414cc7a2c8a4.png)]

從上面這幅圖可以看出,Throwable是java語(yǔ)言中所有錯(cuò)誤和異常的超類(萬(wàn)物即可拋)。它有兩個(gè)子類:Error、Exception。

Java標(biāo)準(zhǔn)庫(kù)內(nèi)建了一些通用的異常,這些類以Throwable為頂層父類。

Throwable又派生出Error類和Exception類。

錯(cuò)誤:Error類以及他的子類的實(shí)例,代表了JVM本身的錯(cuò)誤。錯(cuò)誤不能被程序員通過(guò)代碼處理,Error很少出現(xiàn)。因此,程序員應(yīng)該關(guān)注Exception為父類的分支下的各種異常類。

異常:Exception以及他的子類,代表程序運(yùn)行時(shí)發(fā)送的各種不期望發(fā)生的事件??梢员籎ava異常處理機(jī)制使用,是異常處理的核心。

總體上我們根據(jù)Javac對(duì)異常的處理要求,將異常類分為2類。

非檢查異常(unckecked exception):Error 和 RuntimeException 以及他們的子類。javac在編譯時(shí),不會(huì)提示和發(fā)現(xiàn)這樣的異常,不要求在程序處理這些異常。所以如果愿意,我們可以編寫(xiě)代碼處理(使用try…catch…finally)這樣的異常,也可以不處理。

對(duì)于這些異常,我們應(yīng)該修正代碼,而不是去通過(guò)異常處理器處理 。這樣的異常發(fā)生的原因多半是代碼寫(xiě)的有問(wèn)題。如除0錯(cuò)誤ArithmeticException,錯(cuò)誤的強(qiáng)制類型轉(zhuǎn)換錯(cuò)誤ClassCastException,數(shù)組索引越界ArrayIndexOutOfBoundsException,使用了空對(duì)象NullPointerException等等。

檢查異常(checked exception):除了Error 和 RuntimeException的其它異常。javac強(qiáng)制要求程序員為這樣的異常做預(yù)備處理工作(使用try…catch…finally或者throws)。在方法中要么用try-catch語(yǔ)句捕獲它并處理,要么用throws子句聲明拋出它,否則編譯不會(huì)通過(guò)。

這樣的異常一般是由程序的運(yùn)行環(huán)境導(dǎo)致的。因?yàn)槌绦蚩赡鼙贿\(yùn)行在各種未知的環(huán)境下,而程序員無(wú)法干預(yù)用戶如何使用他編寫(xiě)的程序,于是程序員就應(yīng)該為這樣的異常時(shí)刻準(zhǔn)備著。如SQLException , IOException,ClassNotFoundException 等。

需要明確的是:檢查和非檢查是對(duì)于javac來(lái)說(shuō)的,這樣就很好理解和區(qū)分了。

這部分內(nèi)容摘自http://www.importnew.com/26613.html

初識(shí)異常

異常是在執(zhí)行某個(gè)函數(shù)時(shí)引發(fā)的,而函數(shù)又是層級(jí)調(diào)用,形成調(diào)用棧的,因?yàn)?,只要一個(gè)函數(shù)發(fā)生了異常,那么他的所有的caller都會(huì)被異常影響。當(dāng)這些被影響的函數(shù)以異常信息輸出時(shí),就形成的了異常追蹤棧。

異常最先發(fā)生的地方,叫做異常拋出點(diǎn)。

public class 異常 {
    public static void main (String [] args )
    {
        System . out. println( "----歡迎使用命令行除法計(jì)算器----" ) ;
        CMDCalculate ();
    }
    public static void CMDCalculate ()
    {
        Scanner scan = new Scanner ( System. in );
        int num1 = scan .nextInt () ;
        int num2 = scan .nextInt () ;
        int result = devide (num1 , num2 ) ;
        System . out. println( "result:" + result) ;
        scan .close () ;
    }
    public static int devide (int num1, int num2 ){
        return num1 / num2 ;
    }

//    ----歡迎使用命令行除法計(jì)算器----
//            1
//            0
//    Exception in thread "main" java.lang.ArithmeticException: / by zero
//    at com.javase.異常.異常.devide(異常.java:24)
//    at com.javase.異常.異常.CMDCalculate(異常.java:19)
//    at com.javase.異常.異常.main(異常.java:12)

?

//  ----歡迎使用命令行除法計(jì)算器----
//    r
//    Exception in thread "main" java.util.InputMismatchException
//    at java.util.Scanner.throwFor(Scanner.java:864)
//    at java.util.Scanner.next(Scanner.java:1485)
//    at java.util.Scanner.nextInt(Scanner.java:2117)
//    at java.util.Scanner.nextInt(Scanner.java:2076)
//    at com.javase.異常.異常.CMDCalculate(異常.java:17)
//    at com.javase.異常.異常.main(異常.java:12)

[外鏈圖片轉(zhuǎn)存失敗(img-9rqUQJQj-1569073569354)(http://incdn1.b0.upaiyun.com/2017/09/0b3e4ca2f4cf8d7116c7ad354940601f.png)]

從上面的例子可以看出,當(dāng)devide函數(shù)發(fā)生除0異常時(shí),devide函數(shù)將拋出ArithmeticException異常,因此調(diào)用他的CMDCalculate函數(shù)也無(wú)法正常完成,因此也發(fā)送異常,而CMDCalculate的caller——main 因?yàn)镃MDCalculate拋出異常,也發(fā)生了異常,這樣一直向調(diào)用棧的棧底回溯。

這種行為叫做異常的冒泡,異常的冒泡是為了在當(dāng)前發(fā)生異常的函數(shù)或者這個(gè)函數(shù)的caller中找到最近的異常處理程序。由于這個(gè)例子中沒(méi)有使用任何異常處理機(jī)制,因此異常最終由main函數(shù)拋給JRE,導(dǎo)致程序終止。

上面的代碼不使用異常處理機(jī)制,也可以順利編譯,因?yàn)?個(gè)異常都是非檢查異常。但是下面的例子就必須使用異常處理機(jī)制,因?yàn)楫惓J菣z查異常。

代碼中我選擇使用throws聲明異常,讓函數(shù)的調(diào)用者去處理可能發(fā)生的異常。但是為什么只throws了IOException呢?因?yàn)镕ileNotFoundException是IOException的子類,在處理范圍內(nèi)。

異常和錯(cuò)誤

下面看一個(gè)例子

//錯(cuò)誤即error一般指jvm無(wú)法處理的錯(cuò)誤
//異常是Java定義的用于簡(jiǎn)化錯(cuò)誤處理流程和定位錯(cuò)誤的一種工具。
public class 錯(cuò)誤和錯(cuò)誤 {
    Error error = new Error();

    public static void main(String[] args) {
        throw new Error();
    }

    //下面這四個(gè)異?;蛘咤e(cuò)誤有著不同的處理方法
    public void error1 (){
        //編譯期要求必須處理,因?yàn)檫@個(gè)異常是最頂層異常,包括了檢查異常,必須要處理
        try {
            throw new Throwable();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }
    //Exception也必須處理。否則報(bào)錯(cuò),因?yàn)闄z查異常都繼承自exception,所以默認(rèn)需要捕捉。
    public void error2 (){
        try {
            throw new Exception();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //error可以不處理,編譯不報(bào)錯(cuò),原因是虛擬機(jī)根本無(wú)法處理,所以啥都不用做
    public void error3 (){
        throw new Error();
    }

    //runtimeexception眾所周知編譯不會(huì)報(bào)錯(cuò)
    public void error4 (){
        throw new RuntimeException();
    }
//    Exception in thread "main" java.lang.Error
//    at com.javase.異常.錯(cuò)誤.main(錯(cuò)誤.java:11)

}

異常的處理方式

在編寫(xiě)代碼處理異常時(shí),對(duì)于檢查異常,有2種不同的處理方式:

使用try…catch…finally語(yǔ)句塊處理它。

或者,在函數(shù)簽名中使用throws 聲明交給函數(shù)調(diào)用者caller去解決。

下面看幾個(gè)具體的例子,包括error,exception和throwable

上面的例子是運(yùn)行時(shí)異常,不需要顯示捕獲。
下面這個(gè)例子是可檢查異常需,要顯示捕獲或者拋出。

@Test
public void testException() throws IOException
{
    //FileInputStream的構(gòu)造函數(shù)會(huì)拋出FileNotFoundException
    FileInputStream fileIn = new FileInputStream("E:\\a.txt");

    int word;
    //read方法會(huì)拋出IOException
    while((word =  fileIn.read())!=-1)
    {
        System.out.print((char)word);
    }
    //close方法會(huì)拋出IOException
    fileIn.close();
}

一般情況下的處理方式 try catch finally

public class 異常處理方式 {

@Test
public void main() {
    try{
        //try塊中放可能發(fā)生異常的代碼。
        InputStream inputStream = new FileInputStream("a.txt");

        //如果執(zhí)行完try且不發(fā)生異常,則接著去執(zhí)行finally塊和finally后面的代碼(如果有的話)。
        int i = 1/0;
        //如果發(fā)生異常,則嘗試去匹配catch塊。
        throw new SQLException();
        //使用1.8jdk同時(shí)捕獲多個(gè)異常,runtimeexception也可以捕獲。只是捕獲后虛擬機(jī)也無(wú)法處理,所以不建議捕獲。
    }catch(SQLException | IOException | ArrayIndexOutOfBoundsException exception){
        System.out.println(exception.getMessage());
        //每一個(gè)catch塊用于捕獲并處理一個(gè)特定的異常,或者這異常類型的子類。Java7中可以將多個(gè)異常聲明在一個(gè)catch中。

        //catch后面的括號(hào)定義了異常類型和異常參數(shù)。如果異常與之匹配且是最先匹配到的,則虛擬機(jī)將使用這個(gè)catch塊來(lái)處理異常。

        //在catch塊中可以使用這個(gè)塊的異常參數(shù)來(lái)獲取異常的相關(guān)信息。異常參數(shù)是這個(gè)catch塊中的局部變量,其它塊不能訪問(wèn)。

        //如果當(dāng)前try塊中發(fā)生的異常在后續(xù)的所有catch中都沒(méi)捕獲到,則先去執(zhí)行finally,然后到這個(gè)函數(shù)的外部caller中去匹配異常處理器。

        //如果try中沒(méi)有發(fā)生異常,則所有的catch塊將被忽略。

    }catch(Exception exception){
        System.out.println(exception.getMessage());
        //...
    }finally{
        //finally塊通常是可選的。
        //無(wú)論異常是否發(fā)生,異常是否匹配被處理,finally都會(huì)執(zhí)行。

        //finally主要做一些清理工作,如流的關(guān)閉,數(shù)據(jù)庫(kù)連接的關(guān)閉等。
    }

一個(gè)try至少要跟一個(gè)catch或者finally

    try {
        int i = 1;
    }finally {
        //一個(gè)try至少要有一個(gè)catch塊,否則, 至少要有1個(gè)finally塊。但是finally不是用來(lái)處理異常的,finally不會(huì)捕獲異常。
    }
}

異常出現(xiàn)時(shí)該方法后面的代碼不會(huì)運(yùn)行,即使異常已經(jīng)被捕獲。這里舉出一個(gè)奇特的例子,在catch里再次使用try catch finally

@Test
public void test() {
    try {
        throwE();
        System.out.println("我前面拋出異常了");
        System.out.println("我不會(huì)執(zhí)行了");
    } catch (StringIndexOutOfBoundsException e) {
        System.out.println(e.getCause());
    }catch (Exception ex) {
    //在catch塊中仍然可以使用try catch finally
        try {
            throw new Exception();
        }catch (Exception ee) {
            
        }finally {
            System.out.println("我所在的catch塊沒(méi)有執(zhí)行,我也不會(huì)執(zhí)行的");
        }
    }
}
//在方法聲明中拋出的異常必須由調(diào)用方法處理或者繼續(xù)往上拋,
// 當(dāng)拋到j(luò)re時(shí)由于無(wú)法處理終止程序
public void throwE (){
//        Socket socket = new Socket("127.0.0.1", 80);

        //手動(dòng)拋出異常時(shí),不會(huì)報(bào)錯(cuò),但是調(diào)用該方法的方法需要處理這個(gè)異常,否則會(huì)出錯(cuò)。
//        java.lang.StringIndexOutOfBoundsException
//        at com.javase.異常.異常處理方式.throwE(異常處理方式.java:75)
//        at com.javase.異常.異常處理方式.test(異常處理方式.java:62)
        throw new StringIndexOutOfBoundsException();
    }

其實(shí)有的語(yǔ)言在遇到異常后仍然可以繼續(xù)運(yùn)行

有的編程語(yǔ)言當(dāng)異常被處理后,控制流會(huì)恢復(fù)到異常拋出點(diǎn)接著執(zhí)行,這種策略叫做:resumption model of exception handling(恢復(fù)式異常處理模式 )

而Java則是讓執(zhí)行流恢復(fù)到處理了異常的catch塊后接著執(zhí)行,這種策略叫做:termination model of exception handling(終結(jié)式異常處理模式)

"不負(fù)責(zé)任"的throws

throws是另一種處理異常的方式,它不同于try…catch…finally,throws僅僅是將函數(shù)中可能出現(xiàn)的異常向調(diào)用者聲明,而自己則不具體處理。

采取這種異常處理的原因可能是:方法本身不知道如何處理這樣的異常,或者說(shuō)讓調(diào)用者處理更好,調(diào)用者需要為可能發(fā)生的異常負(fù)責(zé)。

public void foo() throws ExceptionType1 , ExceptionType2 ,ExceptionTypeN
{ 
     //foo內(nèi)部可以拋出 ExceptionType1 , ExceptionType2 ,ExceptionTypeN 類的異常,或者他們的子類的異常對(duì)象。
}

糾結(jié)的finally

finally塊不管異常是否發(fā)生,只要對(duì)應(yīng)的try執(zhí)行了,則它一定也執(zhí)行。只有一種方法讓finally塊不執(zhí)行:System.exit()。因此finally塊通常用來(lái)做資源釋放操作:關(guān)閉文件,關(guān)閉數(shù)據(jù)庫(kù)連接等等。

良好的編程習(xí)慣是:在try塊中打開(kāi)資源,在finally塊中清理釋放這些資源。

需要注意的地方:

1、finally塊沒(méi)有處理異常的能力。處理異常的只能是catch塊。

2、在同一try…catch…finally塊中 ,如果try中拋出異常,且有匹配的catch塊,則先執(zhí)行catch塊,再執(zhí)行finally塊。如果沒(méi)有catch塊匹配,則先執(zhí)行finally,然后去外面的調(diào)用者中尋找合適的catch塊。

3、在同一try…catch…finally塊中 ,try發(fā)生異常,且匹配的catch塊中處理異常時(shí)也拋出異常,那么后面的finally也會(huì)執(zhí)行:首先執(zhí)行finally塊,然后去外圍調(diào)用者中尋找合適的catch塊。

public class finally使用 {
    public static void main(String[] args) {
        try {
            throw new IllegalAccessException();
        }catch (IllegalAccessException e) {
            // throw new Throwable();
            //此時(shí)如果再拋異常,finally無(wú)法執(zhí)行,只能報(bào)錯(cuò)。
            //finally無(wú)論何時(shí)都會(huì)執(zhí)行
            //除非我顯示調(diào)用。此時(shí)finally才不會(huì)執(zhí)行
            System.exit(0);

        }finally {
            System.out.println("算你狠");
        }
    }
}

throw : JRE也使用的關(guān)鍵字

throw exceptionObject

程序員也可以通過(guò)throw語(yǔ)句手動(dòng)顯式的拋出一個(gè)異常。throw語(yǔ)句的后面必須是一個(gè)異常對(duì)象。

throw 語(yǔ)句必須寫(xiě)在函數(shù)中,執(zhí)行throw 語(yǔ)句的地方就是一個(gè)異常拋出點(diǎn),==它和由JRE自動(dòng)形成的異常拋出點(diǎn)沒(méi)有任何差別。==

public void save(User user)
{
      if(user  == null) 
          throw new IllegalArgumentException("User對(duì)象為空");
      //......
 
}

后面開(kāi)始的大部分內(nèi)容都摘自http://www.cnblogs.com/lulipro/p/7504267.html

該文章寫(xiě)的十分細(xì)致到位,令人欽佩,是我目前為之看到關(guān)于異常最詳盡的文章,可以說(shuō)是站在巨人的肩膀上了。

異常調(diào)用鏈

異常的鏈化

在一些大型的,模塊化的軟件開(kāi)發(fā)中,一旦一個(gè)地方發(fā)生異常,則如骨牌效應(yīng)一樣,將導(dǎo)致一連串的異常。假設(shè)B模塊完成自己的邏輯需要調(diào)用A模塊的方法,如果A模塊發(fā)生異常,則B也將不能完成而發(fā)生異常。

==但是B在拋出異常時(shí),會(huì)將A的異常信息掩蓋掉,這將使得異常的根源信息丟失。異常的鏈化可以將多個(gè)模塊的異常串聯(lián)起來(lái),使得異常信息不會(huì)丟失。==

異常鏈化:以一個(gè)異常對(duì)象為參數(shù)構(gòu)造新的異常對(duì)象。新的異對(duì)象將包含先前異常的信息。這項(xiàng)技術(shù)主要是異常類的一個(gè)帶Throwable參數(shù)的函數(shù)來(lái)實(shí)現(xiàn)的。這個(gè)當(dāng)做參數(shù)的異常,我們叫他根源異常(cause)。

查看Throwable類源碼,可以發(fā)現(xiàn)里面有一個(gè)Throwable字段cause,就是它保存了構(gòu)造時(shí)傳遞的根源異常參數(shù)。這種設(shè)計(jì)和鏈表的結(jié)點(diǎn)類設(shè)計(jì)如出一轍,因此形成鏈也是自然的了。

public class Throwable implements Serializable {
    private Throwable cause = this;
 
    public Throwable(String message, Throwable cause) {
        fillInStackTrace();
        detailMessage = message;
        this.cause = cause;
    }
     public Throwable(Throwable cause) {
        fillInStackTrace();
        detailMessage = (cause==null ? null : cause.toString());
        this.cause = cause;
    }
 
    //........
}

下面看一個(gè)比較實(shí)在的異常鏈例子哈

public class 異常鏈 {
    @Test
    public void test() {
        C();
    }
    public void A () throws Exception {
        try {
            int i = 1;
            i = i / 0;
            //當(dāng)我注釋掉這行代碼并使用B方法拋出一個(gè)error時(shí),運(yùn)行結(jié)果如下
//            四月 27, 2018 10:12:30 下午 org.junit.platform.launcher.core.ServiceLoaderTestEngineRegistry loadTestEngines
//            信息: Discovered TestEngines with IDs: [junit-jupiter]
//            java.lang.Error: B也犯了個(gè)錯(cuò)誤
//            at com.javase.異常.異常鏈.B(異常鏈.java:33)
//            at com.javase.異常.異常鏈.C(異常鏈.java:38)
//            at com.javase.異常.異常鏈.test(異常鏈.java:13)
//            at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
//            Caused by: java.lang.Error
//            at com.javase.異常.異常鏈.B(異常鏈.java:29)

        }catch (ArithmeticException e) {
            //這里通過(guò)throwable類的構(gòu)造方法將最底層的異常重新包裝并拋出,此時(shí)注入了A方法的信息。最后打印棧信息時(shí)可以看到caused by
            A方法的異常。
            //如果直接拋出,棧信息打印結(jié)果只能看到上層方法的錯(cuò)誤信息,不能看到其實(shí)是A發(fā)生了錯(cuò)誤。
            //所以需要包裝并拋出
            throw new Exception("A方法計(jì)算錯(cuò)誤", e);
        }

    }
    public void B () throws Exception,Error {
        try {
            //接收到A的異常,
            A();
            throw new Error();
        }catch (Exception e) {
            throw e;
        }catch (Error error) {
            throw new Error("B也犯了個(gè)錯(cuò)誤", error);
        }
    }
    public void C () {
        try {
            B();
        }catch (Exception | Error e) {
            e.printStackTrace();
        }

    }

    //最后結(jié)果
//    java.lang.Exception: A方法計(jì)算錯(cuò)誤
//    at com.javase.異常.異常鏈.A(異常鏈.java:18)
//    at com.javase.異常.異常鏈.B(異常鏈.java:24)
//    at com.javase.異常.異常鏈.C(異常鏈.java:31)
//    at com.javase.異常.異常鏈.test(異常鏈.java:11)
//    省略
//    Caused by: java.lang.ArithmeticException: / by zero
//    at com.javase.異常.異常鏈.A(異常鏈.java:16)
//            ... 31 more
}

自定義異常

如果要自定義異常類,則擴(kuò)展Exception類即可,因此這樣的自定義異常都屬于檢查異常(checked exception)。如果要自定義非檢查異常,則擴(kuò)展自RuntimeException。

按照國(guó)際慣例,自定義的異常應(yīng)該總是包含如下的構(gòu)造函數(shù):

一個(gè)無(wú)參構(gòu)造函數(shù)
一個(gè)帶有String參數(shù)的構(gòu)造函數(shù),并傳遞給父類的構(gòu)造函數(shù)。
一個(gè)帶有String參數(shù)和Throwable參數(shù),并都傳遞給父類構(gòu)造函數(shù)
一個(gè)帶有Throwable 參數(shù)的構(gòu)造函數(shù),并傳遞給父類的構(gòu)造函數(shù)。
下面是IOException類的完整源代碼,可以借鑒。

public class IOException extends Exception
{
    static final long serialVersionUID = 7818375828146090155L;
 
    public IOException()
    {
        super();
    }
 
    public IOException(String message)
    {
        super(message);
    }
 
    public IOException(String message, Throwable cause)
    {
        super(message, cause);
    }
 
    public IOException(Throwable cause)
    {
        super(cause);
    }
}

異常的注意事項(xiàng)

異常的注意事項(xiàng)

當(dāng)子類重寫(xiě)父類的帶有 throws聲明的函數(shù)時(shí),其throws聲明的異常必須在父類異常的可控范圍內(nèi)——用于處理父類的throws方法的異常處理器,必須也適用于子類的這個(gè)帶throws方法 。這是為了支持多態(tài)。

例如,父類方法throws 的是2個(gè)異常,子類就不能throws 3個(gè)及以上的異常。父類throws IOException,子類就必須throws IOException或者IOException的子類。

至于為什么?我想,也許下面的例子可以說(shuō)明。

class Father
{
    public void start() throws IOException
    {
        throw new IOException();
    }
}
 
class Son extends Father
{
    public void start() throws Exception
    {
        throw new SQLException();
    }
}

/**********************假設(shè)上面的代碼是允許的(實(shí)質(zhì)是錯(cuò)誤的)***********************/

class Test
{
    public static void main(String[] args)
    {
        Father[] objs = new Father[2];
        objs[0] = new Father();
        objs[1] = new Son();
 
        for(Father obj:objs)
        {
        //因?yàn)镾on類拋出的實(shí)質(zhì)是SQLException,而IOException無(wú)法處理它。
        //那么這里的try。。catch就不能處理Son中的異常。
        //多態(tài)就不能實(shí)現(xiàn)了。
            try {
                 obj.start();
            }catch(IOException)
            {
                 //處理IOException
            }
         }
   }
}

==Java的異常執(zhí)行流程是線程獨(dú)立的,線程之間沒(méi)有影響==

Java程序可以是多線程的。每一個(gè)線程都是一個(gè)獨(dú)立的執(zhí)行流,獨(dú)立的函數(shù)調(diào)用棧。如果程序只有一個(gè)線程,那么沒(méi)有被任何代碼處理的異常 會(huì)導(dǎo)致程序終止。如果是多線程的,那么沒(méi)有被任何代碼處理的異常僅僅會(huì)導(dǎo)致異常所在的線程結(jié)束。

也就是說(shuō),Java中的異常是線程獨(dú)立的,線程的問(wèn)題應(yīng)該由線程自己來(lái)解決,而不要委托到外部,也不會(huì)直接影響到其它線程的執(zhí)行。

下面看一個(gè)例子

public class 多線程的異常 {
    @Test
    public void test() {
        go();
    }
    public void go () {
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        for (int i = 0;i <= 2;i ++) {
            int finalI = i;
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            executorService.execute(new Runnable() {
                @Override
                //每個(gè)線程拋出異常時(shí)并不會(huì)影響其他線程的繼續(xù)執(zhí)行
                public void run() {
                    try {
                        System.out.println("start thread" + finalI);
                        throw new Exception();
                    }catch (Exception e) {
                        System.out.println("thread" + finalI + " go wrong");
                    }
                }
            });
        }
//        結(jié)果:
//        start thread0
//        thread0 go wrong
//        start thread1
//        thread1 go wrong
//        start thread2
//        thread2 go wrong
    }
}

當(dāng)finally遇上return

首先一個(gè)不容易理解的事實(shí):

在 try塊中即便有return,break,continue等改變執(zhí)行流的語(yǔ)句,finally也會(huì)執(zhí)行。

public static void main(String[] args)
{
    int re = bar();
    System.out.println(re);
}
private static int bar() 
{
    try{
        return 5;
    } finally{
        System.out.println("finally");
    }
}
/*輸出:
finally
*/

很多人面對(duì)這個(gè)問(wèn)題時(shí),總是在歸納執(zhí)行的順序和規(guī)律,不過(guò)我覺(jué)得還是很難理解。我自己總結(jié)了一個(gè)方法。用如下GIF圖說(shuō)明。

[外鏈圖片轉(zhuǎn)存失敗(img-SceF4t85-1569073569354)(http://incdn1.b0.upaiyun.com/2017/09/0471c2805ebd5a463211ced478eaf7f8.gif)]

也就是說(shuō):try…catch…finally中的return 只要能執(zhí)行,就都執(zhí)行了,他們共同向同一個(gè)內(nèi)存地址(假設(shè)地址是0×80)寫(xiě)入返回值,后執(zhí)行的將覆蓋先執(zhí)行的數(shù)據(jù),而真正被調(diào)用者取的返回值就是最后一次寫(xiě)入的。那么,按照這個(gè)思想,下面的這個(gè)例子也就不難理解了。

finally中的return 會(huì)覆蓋 try 或者catch中的返回值。

public static void main(String[] args)
    {
        int result;
 
        result  =  foo();
        System.out.println(result);     /////////2
 
        result = bar();
        System.out.println(result);    /////////2
    }
 
    @SuppressWarnings("finally")
    public static int foo()
    {
        trz{
            int a = 5 / 0;
        } catch (Exception e){
            return 1;
        } finally{
            return 2;
        }
 
    }
 
    @SuppressWarnings("finally")
    public static int bar()
    {
        try {
            return 1;
        }finally {
            return 2;
        }
    }

finally中的return會(huì)抑制(消滅)前面try或者catch塊中的異常

class TestException
{
    public static void main(String[] args)
    {
        int result;
        try{
            result = foo();
            System.out.println(result);           //輸出100
        } catch (Exception e){
            System.out.println(e.getMessage());    //沒(méi)有捕獲到異常
        }
 
        try{
            result  = bar();
            System.out.println(result);           //輸出100
        } catch (Exception e){
            System.out.println(e.getMessage());    //沒(méi)有捕獲到異常
        }
    }
 
    //catch中的異常被抑制
    @SuppressWarnings("finally")
    public static int foo() throws Exception
    {
        try {
            int a = 5/0;
            return 1;
        }catch(ArithmeticException amExp) {
            throw new Exception("我將被忽略,因?yàn)橄旅娴膄inally中使用了return");
        }finally {
            return 100;
        }
    }
 
    //try中的異常被抑制
    @SuppressWarnings("finally")
    public static int bar() throws Exception
    {
        try {
            int a = 5/0;
            return 1;
        }finally {
            return 100;
        }
    }
}

finally中的異常會(huì)覆蓋(消滅)前面try或者catch中的異常

class TestException
{
    public static void main(String[] args)
    {
        int result;
        try{
            result = foo();
        } catch (Exception e){
            System.out.println(e.getMessage());    //輸出:我是finaly中的Exception
        }
 
        try{
            result  = bar();
        } catch (Exception e){
            System.out.println(e.getMessage());    //輸出:我是finaly中的Exception
        }
    }
 
    //catch中的異常被抑制
    @SuppressWarnings("finally")
    public static int foo() throws Exception
    {
        try {
            int a = 5/0;
            return 1;
        }catch(ArithmeticException amExp) {
            throw new Exception("我將被忽略,因?yàn)橄旅娴膄inally中拋出了新的異常");
        }finally {
            throw new Exception("我是finaly中的Exception");
        }
    }
 
    //try中的異常被抑制
    @SuppressWarnings("finally")
    public static int bar() throws Exception
    {
        try {
            int a = 5/0;
            return 1;
        }finally {
            throw new Exception("我是finaly中的Exception");
        }
 
    }
}

上面的3個(gè)例子都異于常人的編碼思維,因此我建議:

不要在fianlly中使用return。

不要在finally中拋出異常。

減輕finally的任務(wù),不要在finally中做一些其它的事情,finally塊僅僅用來(lái)釋放資源是最合適的。

將盡量將所有的return寫(xiě)在函數(shù)的最后面,而不是try … catch … finally中。

JAVA異常常見(jiàn)面試題

下面是我個(gè)人總結(jié)的在Java和J2EE開(kāi)發(fā)者在面試中經(jīng)常被問(wèn)到的有關(guān)Exception和Error的知識(shí)。在分享我的回答的時(shí)候,我也給這些問(wèn)題作了快速修訂,并且提供源碼以便深入理解。我總結(jié)了各種難度的問(wèn)題,適合新手碼農(nóng)和高級(jí)Java碼農(nóng)。如果你遇到了我列表中沒(méi)有的問(wèn)題,并且這個(gè)問(wèn)題非常好,請(qǐng)?jiān)谙旅嬖u(píng)論中分享出來(lái)。你也可以在評(píng)論中分享你面試時(shí)答錯(cuò)的情況。

1) Java中什么是Exception?
  這個(gè)問(wèn)題經(jīng)常在第一次問(wèn)有關(guān)異常的時(shí)候或者是面試菜鳥(niǎo)的時(shí)候問(wèn)。我從來(lái)沒(méi)見(jiàn)過(guò)面高級(jí)或者資深工程師的時(shí)候有人問(wèn)這玩意,但是對(duì)于菜鳥(niǎo),是很愿意問(wèn)這個(gè)的。簡(jiǎn)單來(lái)說(shuō),異常是Java傳達(dá)給你的系統(tǒng)和程序錯(cuò)誤的方式。在java中,異常功能是通過(guò)實(shí)現(xiàn)比如Throwable,Exception,RuntimeException之類的類,然后還有一些處理異常時(shí)候的關(guān)鍵字,比如throw,throws,try,catch,finally之類的。 所有的異常都是通過(guò)Throwable衍生出來(lái)的。Throwable把錯(cuò)誤進(jìn)一步劃分為 java.lang.Exception
和 java.lang.Error. java.lang.Error 用來(lái)處理系統(tǒng)錯(cuò)誤,例如java.lang.StackOverFlowError 之類的。然后 Exception用來(lái)處理程序錯(cuò)誤,請(qǐng)求的資源不可用等等。

2) Java中的檢查型異常和非檢查型異常有什么區(qū)別?

這又是一個(gè)非常流行的Java異常面試題,會(huì)出現(xiàn)在各種層次的Java面試中。檢查型異常和非檢查型異常的主要區(qū)別在于其處理方式。檢查型異常需要使用try, catch和finally關(guān)鍵字在編譯期進(jìn)行處理,否則會(huì)出現(xiàn)編譯器會(huì)報(bào)錯(cuò)。對(duì)于非檢查型異常則不需要這樣做。Java中所有繼承自java.lang.Exception類的異常都是檢查型異常,所有繼承自RuntimeException的異常都被稱為非檢查型異常。

3) Java中的NullPointerException和ArrayIndexOutOfBoundException之間有什么相同之處?

在Java異常面試中這并不是一個(gè)很流行的問(wèn)題,但會(huì)出現(xiàn)在不同層次的初學(xué)者面試中,用來(lái)測(cè)試應(yīng)聘者對(duì)檢查型異常和非檢查型異常的概念是否熟悉。順便說(shuō)一下,該題的答案是,這兩個(gè)異常都是非檢查型異常,都繼承自RuntimeException。該問(wèn)題可能會(huì)引出另一個(gè)問(wèn)題,即Java和C的數(shù)組有什么不同之處,因?yàn)镃里面的數(shù)組是沒(méi)有大小限制的,絕對(duì)不會(huì)拋出ArrayIndexOutOfBoundException。

4)在Java異常處理的過(guò)程中,你遵循的那些最好的實(shí)踐是什么?

這個(gè)問(wèn)題在面試技術(shù)經(jīng)理是非常常見(jiàn)的一個(gè)問(wèn)題。因?yàn)楫惓L幚碓陧?xiàng)目設(shè)計(jì)中是非常關(guān)鍵的,所以精通異常處理是十分必要的。異常處理有很多最佳實(shí)踐,下面列舉集中,它們提高你代碼的健壯性和靈活性:

1) 調(diào)用方法的時(shí)候返回布爾值來(lái)代替返回null,這樣可以 NullPointerException。由于空指針是java異常里最惡心的異常

2) catch塊里別不寫(xiě)代碼??誧atch塊是異常處理里的錯(cuò)誤事件,因?yàn)樗皇遣东@了異常,卻沒(méi)有任何處理或者提示。通常你起碼要打印出異常信息,當(dāng)然你最好根據(jù)需求對(duì)異常信息進(jìn)行處理。

3)能拋受控異常(checked Exception)就盡量不拋受非控異常(checked Exception)。通過(guò)去掉重復(fù)的異常處理代碼,可以提高代碼的可讀性。

4) 絕對(duì)不要讓你的數(shù)據(jù)庫(kù)相關(guān)異常顯示到客戶端。由于絕大多數(shù)數(shù)據(jù)庫(kù)和SQLException異常都是受控異常,在Java中,你應(yīng)該在DAO層把異常信息處理,然后返回處理過(guò)的能讓用戶看懂并根據(jù)異常提示信息改正操作的異常信息。

5) 在Java中,一定要在數(shù)據(jù)庫(kù)連接,數(shù)據(jù)庫(kù)查詢,流處理后,在finally塊中調(diào)用close()方法。

5) 既然我們可以用RuntimeException來(lái)處理錯(cuò)誤,那么你認(rèn)為為什么Java中還存在檢查型異常?

這是一個(gè)有爭(zhēng)議的問(wèn)題,在回答該問(wèn)題時(shí)你應(yīng)當(dāng)小心。雖然他們肯定愿意聽(tīng)到你的觀點(diǎn),但其實(shí)他們最感興趣的還是有說(shuō)服力的理由。我認(rèn)為其中一個(gè)理由是,存在檢查型異常是一個(gè)設(shè)計(jì)上的決定,受到了諸如C++等比Java更早編程語(yǔ)言設(shè)計(jì)經(jīng)驗(yàn)的影響。絕大多數(shù)檢查型異常位于java.io包內(nèi),這是合乎情理的,因?yàn)樵谀阏?qǐng)求了不存在的系統(tǒng)資源的時(shí)候,一段強(qiáng)壯的程序必須能夠優(yōu)雅的處理這種情況。通過(guò)把IOException聲明為檢查型異常,Java 確保了你能夠優(yōu)雅的對(duì)異常進(jìn)行處理。另一個(gè)可能的理由是,可以使用catch或finally來(lái)確保數(shù)量受限的系統(tǒng)資源(比如文件描述符)在你使用后盡早得到釋放。 Joshua
Bloch編寫(xiě)的 Effective Java 一書(shū) 中多處涉及到了該話題,值得一讀。

6) throw 和 throws這兩個(gè)關(guān)鍵字在java中有什么不同?

一個(gè)java初學(xué)者應(yīng)該掌握的面試問(wèn)題。 throw 和 throws乍看起來(lái)是很相似的尤其是在你還是一個(gè)java初學(xué)者的時(shí)候。盡管他們看起來(lái)相似,都是在處理異常時(shí)候使用到的。但在代碼里的使用方法和用到的地方是不同的。throws總是出現(xiàn)在一個(gè)函數(shù)頭中,用來(lái)標(biāo)明該成員函數(shù)可能拋出的各種異常, 你也可以申明未檢查的異常,但這不是編譯器強(qiáng)制的。如果方法拋出了異常那么調(diào)用這個(gè)方法的時(shí)候就需要將這個(gè)異常處理。另一個(gè)關(guān)鍵字 throw 是用來(lái)拋出任意異常的,按照語(yǔ)法你可以拋出任意 Throwable (i.e. Throwable
或任何Throwable的衍生類) , throw可以中斷程序運(yùn)行,因此可以用來(lái)代替return . 最常見(jiàn)的例子是用 throw 在一個(gè)空方法中需要return的地方拋出 UnSupportedOperationException 代碼如下 :

123 private``static void show() {``throw``new UnsupportedOperationException(``"Notyet implemented"``);``}

可以看下這篇 文章查看這兩個(gè)關(guān)鍵字在java中更多的差異 。

7) 什么是“異常鏈”?

“異常鏈”是Java中非常流行的異常處理概念,是指在進(jìn)行一個(gè)異常處理時(shí)拋出了另外一個(gè)異常,由此產(chǎn)生了一個(gè)異常鏈條。該技術(shù)大多用于將“ 受檢查異?!?( checked exception)封裝成為“非受檢查異?!保╱nchecked exception)或者RuntimeException。順便說(shuō)一下,如果因?yàn)橐驗(yàn)楫惓D銢Q定拋出一個(gè)新的異常,你一定要包含原有的異常,這樣,處理程序才可以通過(guò)getCause()和initCause()方法來(lái)訪問(wèn)異常最終的根源。

) 你曾經(jīng)自定義實(shí)現(xiàn)過(guò)異常嗎?怎么寫(xiě)的?

很顯然,我們絕大多數(shù)都寫(xiě)過(guò)自定義或者業(yè)務(wù)異常,像AccountNotFoundException。在面試過(guò)程中詢問(wèn)這個(gè)Java異常問(wèn)題的主要原因是去發(fā)現(xiàn)你如何使用這個(gè)特性的。這可以更準(zhǔn)確和精致的去處理異常,當(dāng)然這也跟你選擇checked 還是unchecked exception息息相關(guān)。通過(guò)為每一個(gè)特定的情況創(chuàng)建一個(gè)特定的異常,你就為調(diào)用者更好的處理異常提供了更好的選擇。相比通用異常(general exception),我更傾向更為精確的異常。大量的創(chuàng)建自定義異常會(huì)增加項(xiàng)目class的個(gè)數(shù),因此,在自定義異常和通用異常之間維持一個(gè)平衡是成功的關(guān)鍵。

9) JDK7中對(duì)異常處理做了什么改變?

這是最近新出的Java異常處理的面試題。JDK7中對(duì)錯(cuò)誤(Error)和異常(Exception)處理主要新增加了2個(gè)特性,一是在一個(gè)catch塊中可以出來(lái)多個(gè)異常,就像原來(lái)用多個(gè)catch塊一樣。另一個(gè)是自動(dòng)化資源管理(ARM), 也稱為try-with-resource塊。這2個(gè)特性都可以在處理異常時(shí)減少代碼量,同時(shí)提高代碼的可讀性。對(duì)于這些特性了解,不僅幫助開(kāi)發(fā)者寫(xiě)出更好的異常處理的代碼,也讓你在面試中顯的更突出。我推薦大家讀一下Java 7攻略,這樣可以更深入的了解這2個(gè)非常有用的特性。

10) 你遇到過(guò) OutOfMemoryError 錯(cuò)誤嘛?你是怎么搞定的?

這個(gè)面試題會(huì)在面試高級(jí)程序員的時(shí)候用,面試官想知道你是怎么處理這個(gè)危險(xiǎn)的OutOfMemoryError錯(cuò)誤的。必須承認(rèn)的是,不管你做什么項(xiàng)目,你都會(huì)碰到這個(gè)問(wèn)題。所以你要是說(shuō)沒(méi)遇到過(guò),面試官肯定不會(huì)買賬。要是你對(duì)這個(gè)問(wèn)題不熟悉,甚至就是沒(méi)碰到過(guò),而你又有3、4年的Java經(jīng)驗(yàn)了,那么準(zhǔn)備好處理這個(gè)問(wèn)題吧。在回答這個(gè)問(wèn)題的同時(shí),你也可以借機(jī)向面試秀一下你處理內(nèi)存泄露、調(diào)優(yōu)和調(diào)試方面的牛逼技能。我發(fā)現(xiàn)掌握這些技術(shù)的人都能給面試官留下深刻的印象。

11) 如果執(zhí)行finally代碼塊之前方法返回了結(jié)果,或者JVM退出了,finally塊中的代碼還會(huì)執(zhí)行嗎?

這個(gè)問(wèn)題也可以換個(gè)方式問(wèn):“如果在try或者finally的代碼塊中調(diào)用了System.exit(),結(jié)果會(huì)是怎樣”。了解finally塊是怎么執(zhí)行的,即使是try里面已經(jīng)使用了return返回結(jié)果的情況,對(duì)了解Java的異常處理都非常有價(jià)值。只有在try里面是有System.exit(0)來(lái)退出JVM的情況下finally塊中的代碼才不會(huì)執(zhí)行。

12)Java中final,finalize,finally關(guān)鍵字的區(qū)別

這是一個(gè)經(jīng)典的Java面試題了。我的一個(gè)朋友為Morgan Stanley招電信方面的核心Java開(kāi)發(fā)人員的時(shí)候就問(wèn)過(guò)這個(gè)問(wèn)題。final和finally是Java的關(guān)鍵字,而finalize則是方法。final關(guān)鍵字在創(chuàng)建不可變的類的時(shí)候非常有用,只是聲明這個(gè)類是final的。而finalize()方法則是垃圾回收器在回收一個(gè)對(duì)象前調(diào)用,但也Java規(guī)范里面沒(méi)有保證這個(gè)方法一定會(huì)被調(diào)用。finally關(guān)鍵字是唯一一個(gè)和這篇文章討論到的異常處理相關(guān)的關(guān)鍵字。在你的產(chǎn)品代碼中,在關(guān)閉連接和資源文件的是時(shí)候都必須要用到finally塊。

參考文章

https://www.xuebuyuan.com/3248044.html
http://www.itdecent.cn/p/49d2c3975c56
http://c.biancheng.net/view/1038.html
https://blog.csdn.net/Lisiluan/article/details/88745820
https://blog.csdn.net/michaelgo/article/details/82790253

微信公眾號(hào)

Java技術(shù)江湖

如果大家想要實(shí)時(shí)關(guān)注我更新的文章以及分享的干貨的話,可以關(guān)注我的公眾號(hào)【Java技術(shù)江湖】一位阿里 Java 工程師的技術(shù)小站,作者黃小斜,專注 Java 相關(guān)技術(shù):SSM、SpringBoot、MySQL、分布式、中間件、集群、Linux、網(wǎng)絡(luò)、多線程,偶爾講點(diǎn)Docker、ELK,同時(shí)也分享技術(shù)干貨和學(xué)習(xí)經(jīng)驗(yàn),致力于Java全棧開(kāi)發(fā)!

Java工程師必備學(xué)習(xí)資源: 一些Java工程師常用學(xué)習(xí)資源,關(guān)注公眾號(hào)后,后臺(tái)回復(fù)關(guān)鍵字 “Java” 即可免費(fèi)無(wú)套路獲取。

我的公眾號(hào)

個(gè)人公眾號(hào):黃小斜

作者是 985 碩士,螞蟻金服 JAVA 工程師,專注于 JAVA 后端技術(shù)棧:SpringBoot、MySQL、分布式、中間件、微服務(wù),同時(shí)也懂點(diǎn)投資理財(cái),偶爾講點(diǎn)算法和計(jì)算機(jī)理論基礎(chǔ),堅(jiān)持學(xué)習(xí)和寫(xiě)作,相信終身學(xué)習(xí)的力量!

程序員3T技術(shù)學(xué)習(xí)資源: 一些程序員學(xué)習(xí)技術(shù)的資源大禮包,關(guān)注公眾號(hào)后,后臺(tái)回復(fù)關(guān)鍵字 “資料” 即可免費(fèi)無(wú)套路獲取。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容