Java異常處理只有Try-Catch嗎?

今天桨踪,我們將討論一個非常重要的主題-Java 中的異常處理。盡管有時可能會對此主題進(jìn)行過多的討論芹啥,但并非每篇文章都包含有用且相關(guān)的信息锻离。

Java 中最常見的異常處理機(jī)制通常與 try-catch 塊關(guān)聯(lián) 铺峭。我們使用它來捕獲異常,然后提供在發(fā)生異常的情況下可以執(zhí)行的邏輯汽纠。

的確卫键,你不需要將所有異常都放在這些塊中。另一方面虱朵,如果你正在研究應(yīng)用程序的軟件設(shè)計莉炉,則可能不需要內(nèi)置的異常處理機(jī)制。在這種情況下碴犬,你可以嘗試使用替代方法-Vavr Try 結(jié)構(gòu)絮宁。

在本文中,我們將探討 Java 異常處理的不同方法服协,并討論如何使用 Vavr Try 替代內(nèi)置方法绍昂。讓我們開始吧!

處理 Java 中的異常

作為介紹偿荷,讓我們回顧一下 Java 如何允許我們處理異常窘游。如果你不記得它,則 Java 中的異常會指出意外或意外事件跳纳,該異常在程序執(zhí)行期間(即在運(yùn)行時)發(fā)生忍饰,這會破壞程序指令的正常流程。Java為我們提供了上述 try-catch 捕獲異常的機(jī)制寺庄。讓我們簡要檢查一下它是如何工作的艾蓝。

如果不處理異常會發(fā)生什么?

首先铣揉,讓我們看一個非常常見的例子饶深。這是一個包含 JDBC 代碼的代碼段:

Connection connection = dataSource.getConnection();
String updateNameSql = "UPDATE employees SET name=? WHERE emp_id=?";
PreparedStatement preparedStatement = connection.prepareStatement(updateNameSql);

坦白地說,你的 IDE 甚至不允許你編寫這段代碼逛拱,而是要求用 try-catch 塊將其包圍敌厘,像這樣:

try {
    Connection connection = dataSource.getConnection();
    String updateNameSql = "UPDATE employees SET name=? WHERE emp_id=?";
    PreparedStatement preparedStatement = connection.prepareStatement(updateNameSql);
} catch (SQLException ex){
}

注:我們可以將其重構(gòu)為 try-with-resources,但是稍后再討論朽合。

那么俱两,為什么我們要這樣編寫代碼?因為 SQLException 是一個檢查異常曹步。

如果這些異诚懿剩可以由方法或構(gòu)造函數(shù)的執(zhí)行拋出并傳播到方法或構(gòu)造函數(shù)邊界之外,則必須在方法或構(gòu)造函數(shù)的 throws 子句中聲明這些異常讲婚。SQLException 如果發(fā)生數(shù)據(jù)庫訪問錯誤尿孔,則在示例中使用的方法將拋出 。因此,我們用一個 try-catch 塊將其包圍活合。

Java 在編譯過程中驗證了這些異常雏婶,這就是它們與運(yùn)行時異常不同的原因。

但是你不必處理所有異常情況

但是白指,并非每個異常都應(yīng)被一個 try-catch 塊包圍留晚。

情況 1:運(yùn)行時異常

Java 異常是 Throwable 的子類,但是其中一些是 RuntimeException 類的子類告嘲〈砦看下面的圖,它給出了 Java 異常的層次結(jié)構(gòu):

請注意橄唬,運(yùn)行時異常是特定的組赋焕。根據(jù) Java 規(guī)范,從這些異常中還是有可能恢復(fù)的轧坎。作為示例宏邮,讓我們回想一下 ArrayIndexOutOfBoundsException泽示「籽看看下面的示例代碼片段:

int numbers[] = [1,43,51,0,9];
System.out.println(numbers[6]);

在這里,我們有一個具有5個值(0-4位)的整數(shù)數(shù)組械筛。當(dāng)我們嘗試檢索絕對超出范圍的值(索引= 6)時捎泻,Java 將拋出 ArrayIndexOutOfBoundsException。

這表明我們嘗試調(diào)用的索引為負(fù)數(shù)埋哟,大于或等于數(shù)組的大小笆豁。如我所說,這些異吵嗌蓿可以修復(fù)闯狱,因此在編譯過程中不會對其進(jìn)行檢查。這意味著你仍然可以編寫如下代碼:

int numbers[] = [1,43,51,0,9];
int index = 6;
try{
    System.out.println(numbers[index]);
} catch (ArrayIndexOutOfBoundsException ex){
    System.out.println("Incorrect index!");
}

但是你不必這樣做抛计。

情況 2:錯誤

Error 是另一個棘手的概念哄孤。再看一下上面的圖-存在錯誤,但是通常不會處理吹截。為什么瘦陈?通常,這是由于 Java 程序無法執(zhí)行任何操作來從錯誤中恢復(fù)波俄,例如:錯誤表明嚴(yán)重的問題晨逝,而合理的應(yīng)用程序甚至不應(yīng)嘗試捕獲。

讓我們考慮一下內(nèi)存錯誤– java.lang.VirtualMachineError懦铺。此錯誤表明 JVM 已損壞或已經(jīng)用盡了繼續(xù)運(yùn)行所必需的資源捉貌。換句話說,如果應(yīng)用程序的內(nèi)存不足,則它根本無法分配額外的內(nèi)存資源趁窃。

當(dāng)然苍匆,如果由于持有大量應(yīng)釋放的內(nèi)存而導(dǎo)致失敗,則異常處理程序可以嘗試釋放它(不是直接釋放它本身棚菊,而是可以調(diào)用JVM來釋放它)浸踩。并且,盡管這樣的處理程序在這種情況下可能有用统求,但是這樣的嘗試可能不會成功检碗。

Try-Catch 塊的變體

上述編寫 try-catch 語句的方法并不是 Java 中唯一可用的方法。還有其他方法:try-with-resources码邻,try-catch-finally 和多個 catch 塊折剃。讓我們快速瀏覽這些不同的方法。

方法 1:Try-With-Resources

try-with-resources 塊在 Java 7 中引入的像屋,并允許開發(fā)者在程序運(yùn)行到此結(jié)束后必須關(guān)閉聲明的資源怕犁。我們可以在實現(xiàn)該 AutoCloseable 接口(即特定標(biāo)記接口)的任何類中包含資源。我們可以像這樣重構(gòu)所提到的 JDBC 代碼:

try (Connection connection = dataSource.getConnection){
    String updateNameSql = "UPDATE employees SET name=? WHERE emp_id=?";
    PreparedStatement preparedStatement = connection.prepareStatement(updateNameSql);
} catch (SQLException ex){
//..
}

Java 確保我們 Connection 在執(zhí)行代碼后將其關(guān)閉己莺。在進(jìn)行此構(gòu)建之前奏甫,我們必須顯式地關(guān)閉 finally 塊中的資源。

方法 2:Try + Finally

finally 塊在任何情況下都將執(zhí)行凌受。例如在成功情況下或在異常情況下阵子。在其中,你需要放置將在之后執(zhí)行的代碼:

FileReader reader = null;
try {
    reader = new FileReader("/text.txt");
    int i=0;
    while(i != -1){
        i = reader.read();
        System.out.println((char) i);
    }
} catch(IOException ex1){
//...
} finally{
    if(reader != null){
       try {
         reader.close();
       } catch (IOException ex2) {
       //...
       }
    }
}

請注意胜蛉,此方法有一個缺點:如果在 finally 塊內(nèi)引發(fā)異常 挠进,則會使其中斷。因此誊册,我們必須正常處理異常领突。將 try-with-resources 與可關(guān)閉的資源一起使用,避免在 finally 塊內(nèi)關(guān)閉資源 案怯。

方法 3:多 Catch 塊

最后君旦,Java 允許我們使用一個 try-catch 塊多次捕獲異常。當(dāng)方法拋出幾種類型的異常并且您想?yún)^(qū)分每種情況的邏輯時殴泰,這很有用于宙。舉個例子,讓這個虛構(gòu)的類使用拋出多個異常的方法:

class Repository{
    void insert(Car car) throws DatabaseAccessException, InvalidInputException {
    //...
    }
}
//...
try {
    repository.insert(car);
} catch (DatabaseAccessException dae){
    System.out.println("Database is down!");
} catch (InvalidInputException iie){
    System.out.println("Invalid format of car!");
}

在這里需要記住什么悍汛?通常捞魁,我們假設(shè)在此代碼中,這些異常處于同一級別离咐。但是你必須從最具體到最一般的順序排序 catch 塊谱俭。例如奉件,捕獲 ArithmeticException 異常必須在 捕獲 Exception 異常之前。

到這里昆著,我們已經(jīng)回顧了如何使用內(nèi)置方法處理 Java 中的異诚孛玻。現(xiàn)在凑懂,讓我們看一下如何使用 Vavr 庫執(zhí)行此操作煤痕。

Vavr Try

我們回顧了捕獲 Java 異常的標(biāo)準(zhǔn)方法。另一種方法是使用 Vavr Try 類接谨,Vavr 是 Java 8+ 中一個函數(shù)式庫摆碉,提供了一些不可變數(shù)據(jù)類型及函數(shù)式控制結(jié)構(gòu)。首先脓豪,添加 Vavr 庫依賴項:

<dependency>
   <groupId>io.vavr</groupId>
   <artifactId>vavr</artifactId>
   <version>0.10.2</version>
</dependency>

Try 容器

Vavr 包含的 Try 類是 monadic 容器類型巷帝,它表示可能導(dǎo)致異常或返回成功計算出的值的計算扫夜。此結(jié)果可以采用 Success 或 Failure楞泼。看下面這段代碼:

class CarsRepository{
    Car insert(Car car) throws DatabaseAccessException {
        //...
    }
    Car find (String id) throws DatabaseAccessException {
        //..
    }
    void update (Car car) throws DatabaseAccessException {
        //..
    }
    void remove (String id) throws DatabaseAccessException {
        //..
    }
}

在調(diào)用此代碼時笤闯,我們將使用這些 try-catch 塊來處理 DatabaseAccessException堕阔。但是另一個解決方案是使用 Vavr 對其進(jìn)行重構(gòu)。查看以下代碼片段:

class CarsVavrRepository{
    Try<Car> insert(Car car) {
        System.out.println("Insert a car...");
        return Try.success(car);
    }
    Try<Car> find (String id) {
        System.out.println("Finding a car...");
        System.out.println("..something wrong with database!");
        return Try.failure(new DatabaseAccessException());
    }
    Try<Car> update (Car car) {
        System.out.println("Updating a car...");
        return Try.success(car);
    }
    Try<Void> remove (String id) {
        System.out.println("Removing a car...");
        System.out.println("..something wrong with database!");
        return Try.failure(new DatabaseAccessException());
    }
}

現(xiàn)在望侈,我們可以使用 Vavr 處理數(shù)據(jù)庫問題印蔬。

處理成功

當(dāng)我們收到成功計算的結(jié)果時,我們會收到 Success:

@Test
void successTest(){
    CarsVavrRepository repository = new CarsVavrRepository();
    Car skoda = new Car("skoda", "9T4 4242", "black");
    Car result = repository.insert(skoda).getOrElse(new Car("volkswagen", "3E2 1222", "red"));
    Assertions.assertEquals(skoda.getColor(), result.getColor());
    Assertions.assertEquals(skoda.getId(), result.getId());
}

請注意脱衙,Vavr.Try 相較于 Vavr.Option,為我們提供了一種方便的 getOrElse 方法例驹,在發(fā)生故障的情況下我們可以使用默認(rèn)值捐韩,我們可以將這種邏輯與有問題的方法結(jié)合使用,例如與 find 一起使用鹃锈。

處理失敗

在另一種情況下荤胁,我們將處理 Failure:

@Test
void failureTest(){
    CarsVavrRepository repository = new CarsVavrRepository();
        // desired car
    Car bmw = new Car("bmw", "4A1 2019", "white");
        // failure car
    Car failureCar = new Car("seat", "1A1 3112", "yellow");
    Car result = repository.find("4A1 2019").getOrElse(failureCar);
    Assertions.assertEquals(bmw.getColor(), result.getColor());
    Assertions.assertEquals(bmw.getId(), result.getId());
}

運(yùn)行此代碼。由于斷言錯誤屎债,該測試將失斀稣:

org.opentest4j.AssertionFailedError: 
Expected :white
Actual   :yellow

這意味著因為我們在 find 方法中對失敗進(jìn)行了硬編碼 ,所以我們收到了默認(rèn)值盆驹。除了返回默認(rèn)值之外圆丹,我們還可以在發(fā)生錯誤的情況下執(zhí)行其他操作并生成結(jié)果。你可以使用鏈接函數(shù) Option 來使您的代碼更具功能性:

repository.insert(bmw).andThen(car -> {
    System.out.println("Car is found "+car.getId());
}).andFinally(()->{
    System.out.println("Finishing");
});

或者躯喇,你可以使用收到的異常執(zhí)行代碼辫封,如下所示:


repository.find("1A9 4312").orElseRun(error->{
    //...
});

一般來說硝枉,Vavr Try 是一個功能豐富的解決方案,可用于以更實用的方式轉(zhuǎn)換代碼庫倦微。毫無疑問妻味,它與其他 Vavr 類(如 Option 或 Collections)結(jié)合后,才可以釋放出真正的力量欣福。但是责球, 如果您想編寫更多的功能樣式的代碼,即使沒有它們拓劝,Vavr Try 對于 Java 的 try-catch 塊來說也是一個的正確的替代選擇棕诵。

總結(jié)

Java 中的異常處理機(jī)制通常與 try-catch 塊關(guān)聯(lián), 以便捕獲異常并提供發(fā)生異常時將要執(zhí)行的邏輯凿将。同樣校套,我們確實不需要將所有異常都放入這些塊中。在本文中牧抵,我們探討了如何使用 Vavr 庫執(zhí)行此操作笛匙。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市犀变,隨后出現(xiàn)的幾起案子妹孙,更是在濱河造成了極大的恐慌,老刑警劉巖获枝,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蠢正,死亡現(xiàn)場離奇詭異,居然都是意外死亡省店,警方通過查閱死者的電腦和手機(jī)嚣崭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來懦傍,“玉大人雹舀,你說我怎么就攤上這事〈志悖” “怎么了说榆?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長寸认。 經(jīng)常有香客問我签财,道長,這世上最難降的妖魔是什么偏塞? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任唱蒸,我火速辦了婚禮,結(jié)果婚禮上烛愧,老公的妹妹穿的比我還像新娘油宜。我一直安慰自己掂碱,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布慎冤。 她就那樣靜靜地躺著疼燥,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蚁堤。 梳的紋絲不亂的頭發(fā)上醉者,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天,我揣著相機(jī)與錄音披诗,去河邊找鬼撬即。 笑死,一個胖子當(dāng)著我的面吹牛呈队,可吹牛的內(nèi)容都是我干的剥槐。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼宪摧,長吁一口氣:“原來是場噩夢啊……” “哼粒竖!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起几于,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤蕊苗,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后沿彭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體朽砰,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年喉刘,在試婚紗的時候發(fā)現(xiàn)自己被綠了瞧柔。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡饱搏,死狀恐怖非剃,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情推沸,我是刑警寧澤,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布券坞,位于F島的核電站鬓催,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏恨锚。R本人自食惡果不足惜宇驾,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望猴伶。 院中可真熱鬧课舍,春花似錦塌西、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至筹淫,卻和暖如春站辉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背损姜。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工饰剥, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人摧阅。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓汰蓉,卻偏偏與公主長得像,于是被迫代替她去往敵國和親棒卷。 傳聞我的和親對象是個殘疾皇子顾孽,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,486評論 2 348

推薦閱讀更多精彩內(nèi)容