Java 中 throw 和 throws 的全面指南
一、throw 關(guān)鍵字
(一)基本概念與作用
throw關(guān)鍵字是Java異常處理機(jī)制中的核心組成部分,它用于在程序中主動(dòng)拋出一個(gè)具體的異常對(duì)象。與系統(tǒng)自動(dòng)拋出的異常不同,throw允許開(kāi)發(fā)者基于業(yè)務(wù)邏輯和特定條件,手動(dòng)觸發(fā)異常處理流程。
當(dāng)程序運(yùn)行過(guò)程中遇到以下情況時(shí),開(kāi)發(fā)者可以使用throw來(lái)手動(dòng)拋出異常:
- 業(yè)務(wù)規(guī)則被違反(如賬戶余額不足)
- 參數(shù)校驗(yàn)失敗
- 資源不可用
- 程序狀態(tài)異常
- 預(yù)期之外的條件發(fā)生
這種機(jī)制會(huì)立即中斷當(dāng)前方法的執(zhí)行流程,并將異常對(duì)象沿著方法調(diào)用棧向上傳遞,直到被合適的catch塊捕獲處理。如果始終未被捕獲,最終會(huì)導(dǎo)致線程終止。
(二)語(yǔ)法規(guī)范與細(xì)節(jié)
完整的throw語(yǔ)法格式如下:
throw new 異常類名([異常信息][, 原因異常]);
其中各組成部分說(shuō)明:
異常類名:必須是
Throwable或其子類- Java內(nèi)置異常:如
IllegalArgumentException、NullPointerException等 - 自定義異常:需繼承
Exception或RuntimeException
- Java內(nèi)置異常:如
異常信息(可選):
- 字符串類型,用于描述異常的具體情況
- 可通過(guò)
getMessage()方法獲取
原因異常(可選):
- 使用異常鏈時(shí),可以指定原始異常
- 語(yǔ)法:
new 異常類名("描述", causeException)
示例變體:
// 基本形式
throw new IllegalArgumentException("參數(shù)無(wú)效");
// 帶原因異常
throw new ServiceException("處理失敗", e);
// 使用預(yù)創(chuàng)建的異常對(duì)象
RuntimeException ex = new RuntimeException();
throw ex;(三)典型應(yīng)用場(chǎng)景
1. 參數(shù)校驗(yàn)場(chǎng)景
當(dāng)方法接收到的參數(shù)違反業(yè)務(wù)規(guī)則時(shí)拋出受檢異?;蚍鞘軝z異常:
/**
* 設(shè)置用戶年齡
* @param age 必須是非負(fù)整數(shù)
* @throws IllegalArgumentException 當(dāng)年齡為負(fù)數(shù)時(shí)拋出
*/
public void setAge(int age) {
if (age < 0) {
throw new IllegalArgumentException("年齡不能為負(fù)數(shù): " + age);
}
this.age = age;
}2. 狀態(tài)檢查場(chǎng)景
當(dāng)對(duì)象處于不合適的操作狀態(tài)時(shí):
public void withdraw(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException("取款金額必須為正數(shù)");
}
if (balance < amount) {
throw new IllegalStateException("賬戶余額不足");
}
balance -= amount;
}3. 資源操作場(chǎng)景
當(dāng)所需資源不可用時(shí):
public void processFile(String path) throws IOException {
File file = new File(path);
if (!file.exists()) {
throw new FileNotFoundException("文件不存在: " + path);
}
// 文件處理邏輯...
}4. 業(yè)務(wù)規(guī)則場(chǎng)景
違反特定業(yè)務(wù)規(guī)則時(shí):
public void placeOrder(Order order) {
if (order.getItems().isEmpty()) {
throw new EmptyOrderException("訂單不能為空");
}
if (!inventory.checkStock(order)) {
throw new InsufficientStockException("庫(kù)存不足");
}
// 訂單處理邏輯...
}
(四)完整示例與執(zhí)行流程
基礎(chǔ)示例代碼
public class ThrowExample {
/**
* 驗(yàn)證年齡有效性
* @param age 待驗(yàn)證的年齡
* @throws IllegalArgumentException 當(dāng)年齡無(wú)效時(shí)拋出
*/
public static void validateAge(int age) {
if (age < 0) {
throw new IllegalArgumentException("年齡不能為負(fù)數(shù): " + age);
}
if (age > 150) {
throw new IllegalArgumentException("年齡超過(guò)合理范圍: " + age);
}
System.out.println("有效年齡: " + age);
}
public static void main(String[] args) {
try {
// 測(cè)試正常情況
validateAge(25);
// 測(cè)試異常情況
validateAge(-5);
// 這行不會(huì)執(zhí)行,因?yàn)樯厦嬉呀?jīng)拋出異常
validateAge(200);
} catch (IllegalArgumentException e) {
System.out.println("捕獲到異常: " + e.getMessage());
// 打印完整的異常堆棧
e.printStackTrace();
}
System.out.println("程序繼續(xù)執(zhí)行...");
}
}執(zhí)行流程分析
- 程序從
main方法開(kāi)始執(zhí)行 - 第一次調(diào)用
validateAge(25):- 參數(shù)通過(guò)校驗(yàn)
- 打印"有效年齡: 25"
- 第二次調(diào)用
validateAge(-5):- 觸發(fā)
age < 0條件 - 拋出
IllegalArgumentException - 方法立即終止,控制權(quán)返回
main方法
- 觸發(fā)
catch塊捕獲異常:- 打印異常信息
- 打印完整堆棧軌跡
- 繼續(xù)執(zhí)行
catch塊后的代碼 validateAge(200)不會(huì)執(zhí)行,因?yàn)榱鞒桃驯恢袛?/li>
控制臺(tái)輸出示例
有效年齡: 25
捕獲到異常: 年齡不能為負(fù)數(shù): -5
java.lang.IllegalArgumentException: 年齡不能為負(fù)數(shù): -5
at ThrowExample.validateAge(ThrowExample.java:7)
at ThrowExample.main(ThrowExample.java:19)
程序繼續(xù)執(zhí)行...
(五)最佳實(shí)踐建議
選擇合適的異常類型:
- 對(duì)于可恢復(fù)錯(cuò)誤,使用受檢異常
- 對(duì)于編程錯(cuò)誤,使用非受檢異常
提供有意義的錯(cuò)誤信息:
// 差 throw new Exception("錯(cuò)誤發(fā)生"); // 好 throw new FileNotFoundException("無(wú)法找到配置文件: " + configPath);考慮異常鏈:
try { // 可能拋出IOException的代碼 } catch (IOException e) { throw new ServiceException("處理文件失敗", e); }避免過(guò)度使用:
- 不要用異常處理常規(guī)控制流程
- 頻繁拋出異常會(huì)影響性能
文檔化異常:
/** * @throws IllegalArgumentException 當(dāng)輸入?yún)?shù)無(wú)效時(shí) * @throws IllegalStateException 當(dāng)對(duì)象狀態(tài)不適合操作時(shí) */ public void doSomething() { // 方法實(shí)現(xiàn) }
二、throws 關(guān)鍵字
(一)基本概念
throws關(guān)鍵字是Java異常處理機(jī)制的重要組成部分,用于在方法聲明處指明該方法可能拋出的異常類型。它的核心作用是:
- 作為方法簽名的一部分,向調(diào)用者明確傳達(dá)該方法在執(zhí)行過(guò)程中可能會(huì)遇到哪些異常情況
- 將異常處理的責(zé)任轉(zhuǎn)移給方法的調(diào)用方,實(shí)現(xiàn)異常傳播
- 強(qiáng)制要求調(diào)用者處理受檢異常,提高代碼的健壯性
(二)語(yǔ)法格式詳解
完整的語(yǔ)法結(jié)構(gòu)如下:
[訪問(wèn)修飾符] [static] [final] [abstract] [synchronized] 返回值類型 方法名(參數(shù)列表)
throws 異常類型1, 異常類型2, ..., 異常類型N {
// 方法實(shí)現(xiàn)
}語(yǔ)法說(shuō)明:
- throws關(guān)鍵字必須放在方法參數(shù)列表之后,方法體之前
- 可以聲明多個(gè)異常類型,用英文逗號(hào)分隔
- 聲明的異常類型必須是Throwable或其子類
- 如果方法重寫(xiě)父類方法,throws聲明的異常不能比父類方法聲明的異常范圍更廣
(三)使用場(chǎng)景深入分析
受檢異常(Checked Exception)處理:
- 必須處理的情況:當(dāng)方法內(nèi)部可能拋出IOException、SQLException等受檢異常
- 典型場(chǎng)景:文件操作(如FileInputStream)、數(shù)據(jù)庫(kù)操作(如JDBC)、網(wǎng)絡(luò)通信等
- 處理方式選擇:要么在方法內(nèi)用try-catch處理,要么用throws聲明
非受檢異常(Unchecked Exception)處理:
- 可選處理:RuntimeException及其子類(如NullPointerException)
- 最佳實(shí)踐:雖然可以不聲明,但建議對(duì)重要的運(yùn)行時(shí)異常進(jìn)行顯式聲明
- 框架應(yīng)用:Spring等框架中常用RuntimeException聲明業(yè)務(wù)異常
異常傳播場(chǎng)景:
- 多層方法調(diào)用時(shí)的異常傳遞
- 框架設(shè)計(jì)中的異常處理策略
- 自定義異常體系的構(gòu)建
(四)完整示例代碼與解析
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
public class ComprehensiveThrowsExample {
/**
* 聲明多個(gè)異常類型的示例
* @param filePath 文件路徑
* @throws FileNotFoundException 當(dāng)文件不存在時(shí)拋出
* @throws IOException 當(dāng)發(fā)生IO錯(cuò)誤時(shí)拋出
*/
public static void processFile(String filePath)
throws FileNotFoundException, IOException {
FileInputStream fis = new FileInputStream(filePath);
// 模擬IO操作
int data = fis.read();
while(data != -1) {
System.out.print((char)data);
data = fis.read();
}
fis.close();
}
/**
* 自定義異常和運(yùn)行時(shí)異常示例
* @param dateStr 日期字符串
* @throws ParseException 當(dāng)日期格式錯(cuò)誤時(shí)拋出
* @throws IllegalArgumentException 當(dāng)參數(shù)不合法時(shí)拋出
*/
public static void parseDate(String dateStr)
throws ParseException, IllegalArgumentException {
if(dateStr == null || dateStr.isEmpty()) {
throw new IllegalArgumentException("日期字符串不能為空");
}
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
sdf.parse(dateStr);
}
public static void main(String[] args) {
try {
// 測(cè)試文件處理方法
processFile("config.ini");
// 測(cè)試日期解析方法
parseDate("2023-05-15");
} catch (FileNotFoundException e) {
System.err.println("文件未找到異常: " + e.getMessage());
} catch (IOException e) {
System.err.println("IO異常: " + e.getMessage());
} catch (ParseException e) {
System.err.println("日期解析異常: " + e.getMessage());
} catch (IllegalArgumentException e) {
System.err.println("參數(shù)異常: " + e.getMessage());
} finally {
System.out.println("\n程序執(zhí)行完畢");
}
}
}代碼解析:
- processFile方法展示了如何處理多個(gè)受檢異常
- parseDate方法展示了受檢異常和運(yùn)行時(shí)異常的混合聲明
- main方法展示了完整的異常捕獲和處理流程
- 包含了try-catch-finally的完整異常處理結(jié)構(gòu)
- 演示了不同類型異常的分層次處理方式
三、throw 和 throws 的區(qū)別
一、本質(zhì)與作用的根本差異
(一)throw 的本質(zhì)與深入解析
throw是一個(gè)執(zhí)行性語(yǔ)句,其核心作用是在程序運(yùn)行過(guò)程中,當(dāng)檢測(cè)到不符合預(yù)期的情況時(shí),主動(dòng)創(chuàng)建并拋出一個(gè)具體的異常對(duì)象,直接中斷當(dāng)前的程序執(zhí)行流程。它就像一個(gè)"警報(bào)觸發(fā)器",一旦執(zhí)行,就會(huì)立即發(fā)出異常警報(bào),迫使程序進(jìn)入異常處理流程。
具體來(lái)說(shuō),throw的工作機(jī)制包含以下關(guān)鍵點(diǎn):
- 即時(shí)性:throw語(yǔ)句執(zhí)行時(shí)會(huì)立即終止當(dāng)前方法的繼續(xù)執(zhí)行
- 對(duì)象創(chuàng)建:必須配合new關(guān)鍵字創(chuàng)建一個(gè)具體的異常類實(shí)例
- 異常傳播:拋出的異常會(huì)沿著調(diào)用棧向上傳播,直到被catch捕獲或?qū)е鲁绦蚪K止
例如,在一個(gè)計(jì)算除法的方法中,當(dāng)除數(shù)為0時(shí),使用throw拋出異常:
public static int divide(int a, int b) {
if (b == 0) {
// 創(chuàng)建并拋出ArithmeticException異常實(shí)例
throw new ArithmeticException("除數(shù)不能為0");
// 從這里開(kāi)始后續(xù)代碼不會(huì)執(zhí)行
}
return a / b;
}
當(dāng)b為0時(shí),throw語(yǔ)句會(huì):
- 實(shí)例化一個(gè)ArithmeticException對(duì)象,附帶錯(cuò)誤信息"除數(shù)不能為0"
- 立即終止divide方法的執(zhí)行(return語(yǔ)句不會(huì)被執(zhí)行)
- 異常對(duì)象會(huì)傳遞給調(diào)用divide方法的上層代碼
throw的典型應(yīng)用場(chǎng)景還包括:
- 參數(shù)合法性校驗(yàn)
- 業(yè)務(wù)規(guī)則違反情況
- 資源不可用狀態(tài)
- 預(yù)期外的程序狀態(tài)
(二)throws 的本質(zhì)與深入解析
throws是一個(gè)聲明性標(biāo)記,它的作用是在方法定義時(shí),向方法的調(diào)用者聲明該方法在執(zhí)行過(guò)程中可能會(huì)拋出的異常類型,屬于一種"風(fēng)險(xiǎn)告知"。它本身不會(huì)拋出異常,也不會(huì)影響方法的正常執(zhí)行流程,只是提前告知調(diào)用者可能面臨的異常風(fēng)險(xiǎn),以便調(diào)用者做好應(yīng)對(duì)準(zhǔn)備。
throws關(guān)鍵點(diǎn)的核心特征:
- 聲明性而非執(zhí)行性:只是標(biāo)注可能拋出的異常類型
- 編譯時(shí)檢查:幫助編譯器進(jìn)行異常處理檢查
- 方法契約:構(gòu)成方法簽名的一部分,是調(diào)用者必須知曉的接口規(guī)范
例如,一個(gè)讀取文件的方法聲明可能拋出的異常:
public static String readFileContent(String filePath)
throws FileNotFoundException, IOException {
// 實(shí)際讀取文件的代碼
// 可能拋出:
// - FileNotFoundException 當(dāng)文件不存在時(shí)
// - IOException 當(dāng)讀取過(guò)程中發(fā)生I/O錯(cuò)誤時(shí)
}
throws聲明的幾個(gè)重要特點(diǎn):
- 可以聲明多個(gè)異常類型,用逗號(hào)分隔
- 聲明的異常包括方法體內(nèi)部通過(guò)throw顯式拋出的異常,以及調(diào)用的其他方法拋出的未捕獲異常
- 對(duì)于非運(yùn)行時(shí)異常(RuntimeException及其子類外的異常),調(diào)用者必須處理或繼續(xù)聲明
throws的典型應(yīng)用場(chǎng)景包括:
- 可能失敗的操作(如I/O操作)
- 依賴外部資源的方法
- 執(zhí)行前提條件可能不滿足的操作
通過(guò)throws聲明,方法的調(diào)用者可以清晰地了解需要處理哪些異常情況,從而編寫(xiě)更健壯的代碼。
二、語(yǔ)法與使用位置的不同
(一)throw 的語(yǔ)法與位置
throw 語(yǔ)句只能用于方法體內(nèi)部或構(gòu)造器內(nèi)部,用于顯式地拋出一個(gè)異常對(duì)象。其完整的語(yǔ)法格式為:
throw new 異常類名(異常描述信息);
其中:
new 異常類名(異常描述信息)用于創(chuàng)建一個(gè)具體的異常實(shí)例- 異常描述信息是一個(gè)字符串,用于說(shuō)明異常發(fā)生的原因
- throw 后面必須緊跟一個(gè)異常對(duì)象實(shí)例,不能直接跟異常類型
常見(jiàn)錯(cuò)誤示例:
throw ArithmeticException; // 錯(cuò)誤:缺少new關(guān)鍵字創(chuàng)建對(duì)象 throw "計(jì)算錯(cuò)誤"; // 錯(cuò)誤:不能直接拋出字符串 throw null; // 錯(cuò)誤:不能拋出null
正確示例:
throw new ArithmeticException("除數(shù)不能為零"); // 拋出運(yùn)行時(shí)異常
throw new FileNotFoundException("文件未找到: config.ini"); // 拋出檢查異常
throw new IllegalArgumentException("年齡不能為負(fù)數(shù): " + age); // 帶詳細(xì)錯(cuò)誤信息
應(yīng)用場(chǎng)景:
- 在方法中檢測(cè)到非法參數(shù)時(shí)
- 業(yè)務(wù)邏輯不滿足執(zhí)行條件時(shí)
- 需要將底層異常轉(zhuǎn)換為更合適的異常類型時(shí)
(二)throws 的語(yǔ)法與位置
throws 關(guān)鍵字只能用于方法聲明的尾部,位于方法參數(shù)列表之后、方法體之前。其完整的語(yǔ)法格式為:
修飾符 返回值類型 方法名(參數(shù)列表) throws 異常類型1, 異常類型2, ... {
// 方法體
}
關(guān)鍵點(diǎn):
- throws 后面跟的是一個(gè)或多個(gè)異常類型
- 多個(gè)異常類型之間用逗號(hào)分隔
- 只能聲明檢查異常(checked exception),運(yùn)行時(shí)異常不需要聲明
- 子類方法重寫(xiě)時(shí),throws 的異常不能比父類方法更寬泛
正確示例:
// 聲明單個(gè)異常
public void readFile() throws IOException {
// 讀取文件操作
}
// 聲明多個(gè)異常
public void processData() throws SQLException, ParseException {
// 數(shù)據(jù)庫(kù)操作和日期解析
}
// 繼承中的異常聲明
class Parent {
void demo() throws IOException {}
}
class Child extends Parent {
@Override
void demo() throws FileNotFoundException { // FileNotFoundException是IOException的子類
// 實(shí)現(xiàn)代碼
}
}應(yīng)用場(chǎng)景:
- 方法內(nèi)部調(diào)用了可能拋出檢查異常的方法
- 方法內(nèi)部使用throw拋出了檢查異常
- 需要明確告知調(diào)用方可能出現(xiàn)的異常情況
三、異常處理責(zé)任的承擔(dān)方式
(一)throw 的異常處理責(zé)任
異常拋出與處理機(jī)制
當(dāng)使用throw關(guān)鍵字拋出異常后,Java提供了兩種標(biāo)準(zhǔn)的異常處理途徑:
在當(dāng)前方法內(nèi)捕獲并處理異常
這是通過(guò)try-catch塊實(shí)現(xiàn)的本地化處理方式。這種方式適合處理那些能夠在當(dāng)前方法內(nèi)完全解決的異常情況。
public static void checkNumber(int num) { try { if (num < 0) { throw new IllegalArgumentException("數(shù)字不能為負(fù)數(shù)"); } System.out.println("輸入的數(shù)字是:" + num); } catch (IllegalArgumentException e) { System.out.println("捕獲到異常:" + e.getMessage()); // 可以添加恢復(fù)邏輯,比如設(shè)置默認(rèn)值 System.out.println("將使用默認(rèn)值0代替"); num = 0; } }將異常傳遞給調(diào)用者處理
如果當(dāng)前方法不適合處理該異常,可以在方法簽名中使用throws關(guān)鍵字聲明該異常,將處理責(zé)任轉(zhuǎn)移給上層調(diào)用者。
public static void checkNumber(int num) throws IllegalArgumentException { if (num < 0) { throw new IllegalArgumentException("數(shù)字不能為負(fù)數(shù)"); } System.out.println("輸入的數(shù)字是:" + num); }
異常類型與處理要求
Java中的異常分為兩類,處理要求有所不同:
受檢異常(Checked Exception)
- 如IOException、SQLException等
- 必須被捕獲或在方法簽名中聲明
- 編譯器會(huì)強(qiáng)制檢查
- 示例:
public void readFile(String path) throws IOException { if (!new File(path).exists()) { throw new IOException("文件不存在"); } // 文件讀取邏輯 }
非受檢異常(Unchecked Exception)
- 包括RuntimeException及其子類
- 不強(qiáng)制要求捕獲或聲明
- 通常是程序邏輯錯(cuò)誤
- 示例:
public void divide(int a, int b) { if (b == 0) { throw new ArithmeticException("除數(shù)不能為零"); } System.out.println("結(jié)果:" + (a / b)); }
(二)throws 的異常處理責(zé)任
throws關(guān)鍵字的本質(zhì)
throws關(guān)鍵字本身并不處理異常,它只是將異常處理的責(zé)任轉(zhuǎn)移給方法的調(diào)用者。這種機(jī)制形成了異常傳播鏈,直到異常被處理或到達(dá)調(diào)用棧頂端。
調(diào)用者的處理選擇
調(diào)用者在調(diào)用聲明了throws的方法時(shí),有以下兩種處理方式:
立即捕獲并處理異常
這種方法適合調(diào)用者能夠處理異常的情況,可以避免異常繼續(xù)向上傳播。
public static void main(String[] args) { try { readFileContent("test.txt"); System.out.println("文件讀取成功"); } catch (FileNotFoundException e) { System.err.println("錯(cuò)誤:文件未找到 - " + e.getMessage()); // 恢復(fù)邏輯:創(chuàng)建新文件或提示用戶 } catch (IOException e) { System.err.println("IO錯(cuò)誤:" + e.getMessage()); // 其他IO異常處理 } finally { System.out.println("文件操作處理完成"); } }繼續(xù)向上傳遞異常
當(dāng)調(diào)用者也無(wú)法處理異常時(shí),可以繼續(xù)向上傳遞異常,直到有合適的方法處理它。
public static void processFile() throws FileNotFoundException, IOException { readFileContent("test.txt"); // 其他文件處理邏輯 } public static void main(String[] args) throws FileNotFoundException, IOException { processFile(); // 異常繼續(xù)向上傳遞 }
異常傳播終點(diǎn)
當(dāng)異常一直傳遞到main方法仍然未被捕獲,且main方法也使用throws聲明異常時(shí):
- 異常會(huì)被傳遞給JVM處理
- JVM的默認(rèn)處理方式是:
- 打印異常堆棧跟蹤信息
- 終止當(dāng)前線程的執(zhí)行
- 示例:
public static void main(String[] args) throws FileNotFoundException, IOException { readFileContent("missing.txt"); // 如果文件不存在,程序?qū)⒔K止 System.out.println("這行代碼不會(huì)被執(zhí)行"); }
最佳實(shí)踐建議
合理使用throws
- 只拋出調(diào)用者能夠合理處理的異常
- 避免過(guò)度使用throws聲明不必要的異常
異常處理層級(jí)
// 底層方法:專注于業(yè)務(wù)邏輯,拋出具體異常 public void saveData(Data data) throws SQLException { // 數(shù)據(jù)庫(kù)操作 } // 中層方法:整合多個(gè)操作,轉(zhuǎn)換異常類型 public void processTransaction(Data data) throws TransactionException { try { validate(data); saveData(data); logTransaction(data); } catch (SQLException e) { throw new TransactionException("交易處理失敗", e); } } // 頂層方法:處理用戶交互 public void handleUserRequest(Data data) { try { processTransaction(data); } catch (TransactionException e) { showErrorMessage(e.getMessage()); logger.error("交易異常", e); } }異常包裝
- 將底層異常包裝為更高層次的業(yè)務(wù)異常
- 保留原始異常信息
try { // 可能拋出SQLException的代碼 } catch (SQLException e) { throw new BusinessException("數(shù)據(jù)保存失敗", e); }
四、與異常類型的關(guān)聯(lián)差異
(一)throw 與異常類型
throw 語(yǔ)句用于主動(dòng)拋出異常對(duì)象,這個(gè)對(duì)象可以是任何合法的異常類實(shí)例,具體包括:
Java 內(nèi)置異常類:
- 運(yùn)行時(shí)異常:如
NullPointerException、ArrayIndexOutOfBoundsException、IllegalArgumentException等 - 檢查型異常:如
IOException、SQLException、ClassNotFoundException等
- 運(yùn)行時(shí)異常:如
自定義異常類:
- 必須繼承自
Exception類或其子類 - 對(duì)于運(yùn)行時(shí)異常,可以繼承
RuntimeException - 自定義異常通常需要提供至少兩個(gè)構(gòu)造方法:無(wú)參構(gòu)造和帶消息參數(shù)的構(gòu)造
- 必須繼承自
示例代碼:拋出自定義異常
// 自定義檢查型異常
class MyBusinessException extends Exception {
// 無(wú)參構(gòu)造
public MyBusinessException() {
super("業(yè)務(wù)異常發(fā)生");
}
// 帶詳細(xì)消息的構(gòu)造
public MyBusinessException(String message) {
super(message);
}
// 帶原因異常的構(gòu)造
public MyBusinessException(String message, Throwable cause) {
super(message, cause);
}
}
public class ExceptionDemo {
// 方法聲明可能拋出自定義異常
public static void validate(int value) throws MyBusinessException {
if (value < 0) {
// 拋出自定義異常實(shí)例
throw new MyBusinessException("數(shù)值不能為負(fù)數(shù)");
}
System.out.println("驗(yàn)證通過(guò),值為:" + value);
}
}(二)throws 與異常類型
throws 關(guān)鍵字用于方法簽名中聲明可能拋出的異常類型,需要遵循以下規(guī)則:
聲明原則:
- 必須聲明方法內(nèi)部實(shí)際可能拋出的檢查型異常
- 可以聲明實(shí)際拋出異常的父類
- 運(yùn)行時(shí)異常(RuntimeException及其子類)不需要聲明
聲明規(guī)則:
- 可以聲明多個(gè)異常類型,用逗號(hào)分隔
- 子類方法重寫(xiě)父類方法時(shí),throws 聲明的異常不能比父類方法聲明的異常更寬泛
示例代碼:throws 聲明
import java.io.*;
public class FileProcessor {
// 正確:聲明實(shí)際拋出的具體異常
public static void readConfigFile(String path) throws FileNotFoundException {
FileInputStream fis = new FileInputStream(path);
// 其他處理...
}
// 正確:聲明異常的父類
public static void copyFile(String src, String dest) throws IOException {
FileInputStream in = new FileInputStream(src);
FileOutputStream out = new FileOutputStream(dest);
// 文件復(fù)制操作...
}
// 錯(cuò)誤示例:聲明了不可能拋出的異常(編譯時(shí)不會(huì)報(bào)錯(cuò),但邏輯錯(cuò)誤)
public static void listFiles(String dir) throws SQLException {
File folder = new File(dir);
for (File file : folder.listFiles()) {
System.out.println(file.getName());
}
}
// 正確:聲明多個(gè)異常
public static void processFile(String path)
throws FileNotFoundException, SecurityException {
// 可能拋出兩種異常的操作
}
}方法重寫(xiě)時(shí)的 throws 規(guī)則
class Parent {
public void demo() throws IOException {
// 父類實(shí)現(xiàn)
}
}
class Child extends Parent {
// 正確:可以不聲明任何異常
@Override
public void demo() {
// 子類實(shí)現(xiàn)
}
// 正確:可以聲明相同的異常
@Override
public void demo() throws IOException {
// 子類實(shí)現(xiàn)
}
// 正確:可以聲明更具體的異常
@Override
public void demo() throws FileNotFoundException {
// 子類實(shí)現(xiàn)
}
// 錯(cuò)誤:不能聲明更寬泛的異常
// @Override
// public void demo() throws Exception {
// // 編譯錯(cuò)誤
// }
}五、在繼承中的表現(xiàn)差異
在類的繼承關(guān)系中,子類重寫(xiě)父類方法時(shí),throw和throws的使用存在不同的限制。
(一)throw 在繼承中的表現(xiàn)
在面向?qū)ο罄^承關(guān)系中,子類重寫(xiě)父類方法時(shí),關(guān)于 throw 語(yǔ)句的使用有以下詳細(xì)規(guī)則:
相同異常:子類方法可以拋出與父類方法完全相同的異常類型。例如父類方法拋出
IOException,子類方法也可以拋出IOException。子類異常:子類方法可以拋出父類方法所拋出異常的子類異常。這是多態(tài)性的體現(xiàn),因?yàn)樽宇惍惓?梢员桓割惍惓R貌东@。例如:
- 父類拋出
IOException - 子類可以拋出
FileNotFoundException(IOException的子類)
- 父類拋出
不拋出異常:子類方法可以選擇不拋出任何異常,即使父類方法聲明了異常。
運(yùn)行時(shí)異常:無(wú)論父類方法是否聲明異常,子類方法都可以自由拋出運(yùn)行時(shí)異常(
RuntimeException及其子類),因?yàn)檫\(yùn)行時(shí)異常不受檢查。
實(shí)際應(yīng)用場(chǎng)景示例:
class DatabaseReader {
public void readData() throws SQLException {
// 讀取數(shù)據(jù)庫(kù)
}
}
class MySQLReader extends DatabaseReader {
@Override
public void readData() throws SQLException {
// 可以拋出相同的SQLException
}
}
class OracleReader extends DatabaseReader {
@Override
public void readData() {
// 選擇不拋出任何異常
}
}
class CustomReader extends DatabaseReader {
@Override
public void readData() throws BatchUpdateException {
// 拋出SQLException的子類BatchUpdateException
}
}(二)throws 在繼承中的表現(xiàn)
子類重寫(xiě)父類方法時(shí),throws 聲明的異常受到更嚴(yán)格的限制:
異常范圍限制:
- 子類方法聲明的異常類型必須是父類方法聲明異常的子類或完全相同的類型
- 不能聲明比父類方法更寬泛的異常類型
- 例如父類聲明
IOException,子類可以聲明FileNotFoundException,但不能聲明Exception
父類無(wú)throws聲明的情況:
- 如果父類方法沒(méi)有使用 throws 聲明任何受檢異常
- 子類方法也不能聲明任何受檢異常
- 但可以拋出非受檢異常(RuntimeException及其子類)
多重異常聲明:
- 父類方法聲明多個(gè)異常時(shí)
- 子類方法可以選擇聲明這些異常的子集或子類異常
- 但不能添加新的受檢異常類型
更詳細(xì)的示例說(shuō)明:
class Parent {
// 情況1:父類聲明IO異常
public void doIO() throws IOException {
// IO操作
}
// 情況2:父類不聲明任何異常
public void doNothing() {
// 無(wú)異常操作
}
}
class CorrectChild extends Parent {
// 合法重寫(xiě):聲明完全相同的異常
@Override
public void doIO() throws IOException {
// ...
}
// 合法重寫(xiě):聲明更具體的異常
@Override
public void doIO() throws FileNotFoundException {
// ...
}
// 合法重寫(xiě):不聲明任何異常
@Override
public void doNothing() {
// ...
}
// 合法重寫(xiě):聲明運(yùn)行時(shí)異常
@Override
public void doNothing() throws NullPointerException {
// ...
}
}
class WrongChild extends Parent {
// 非法重寫(xiě):聲明更寬泛的異常
@Override
public void doIO() throws Exception { // 編譯錯(cuò)誤
// ...
}
// 非法重寫(xiě):聲明新的受檢異常
@Override
public void doNothing() throws SQLException { // 編譯錯(cuò)誤
// ...
}
}這些規(guī)則確保了里氏替換原則(LSP)在異常處理中的實(shí)現(xiàn),保證子類對(duì)象可以替換父類對(duì)象而不破壞程序的正確性。
六、使用數(shù)量的差異
(一)throw 語(yǔ)句的使用數(shù)量與執(zhí)行特性
在一個(gè)方法體內(nèi)可以包含多個(gè)throw語(yǔ)句,這是完全合法的語(yǔ)法結(jié)構(gòu)。但需要注意的是,程序執(zhí)行時(shí)只會(huì)有一個(gè)throw語(yǔ)句被執(zhí)行。這是因?yàn)橐坏┠硞€(gè)throw語(yǔ)句被執(zhí)行(即拋出異常),程序會(huì)立即中斷當(dāng)前方法的執(zhí)行流程,后續(xù)的代碼(包括其他throw語(yǔ)句)都不會(huì)被執(zhí)行。
示例代碼分析:
public static void checkParams(String str, int num) {
if (str == null) {
throw new NullPointerException("字符串不能為null"); // 第一個(gè)throw
}
if (num < 0) {
throw new IllegalArgumentException("數(shù)字不能為負(fù)數(shù)"); // 第二個(gè)throw
}
}
在這個(gè)例子中,存在兩種可能的執(zhí)行路徑:
當(dāng)str為null時(shí):
- 第一個(gè)條件判斷成立
- 執(zhí)行第一個(gè)throw語(yǔ)句,拋出NullPointerException
- 方法立即終止,第二個(gè)條件判斷和throw語(yǔ)句都不會(huì)執(zhí)行
當(dāng)str不為null但num為負(fù)數(shù)時(shí):
- 第一個(gè)條件判斷不成立
- 繼續(xù)執(zhí)行第二個(gè)條件判斷
- 第二個(gè)throw語(yǔ)句被執(zhí)行,拋出IllegalArgumentException
- 方法終止
當(dāng)兩個(gè)參數(shù)都合法時(shí):
- 兩個(gè)條件判斷都不成立
- 方法正常執(zhí)行完畢,不拋出任何異常
實(shí)際應(yīng)用場(chǎng)景: 這種多throw語(yǔ)句的結(jié)構(gòu)常用于參數(shù)校驗(yàn)或前置條件檢查,可以針對(duì)不同的錯(cuò)誤情況拋出特定的異常類型,使錯(cuò)誤信息更加精確。例如在Web開(kāi)發(fā)中,處理API請(qǐng)求參數(shù)時(shí)經(jīng)常會(huì)使用這種模式。
(二)throws 子句的多異常聲明
在方法聲明時(shí),可以使用throws關(guān)鍵字來(lái)聲明該方法可能拋出的多個(gè)異常類型。這些異常類型需要用逗號(hào)分隔,形成一個(gè)異常類型列表。
示例代碼:
public static void complexOperation()
throws IOException, SQLException, ParseException {
// 可能拋出多種異常的代碼
// 比如:
// 1. 文件讀寫(xiě)操作可能拋出IOException
// 2. 數(shù)據(jù)庫(kù)操作可能拋出SQLException
// 3. 日期解析可能拋出ParseException
}
關(guān)鍵特性:
異常類型順序:
- 多個(gè)異常類型的聲明順序通常沒(méi)有強(qiáng)制要求
- 但按照從具體到抽象的順序排列更符合規(guī)范(如先子類異常后父類異常)
使用場(chǎng)景:
- 當(dāng)方法內(nèi)部可能拋出多種不同類型的檢查異常(checked exception)時(shí)
- 適用于方法內(nèi)部調(diào)用了多個(gè)可能拋出不同異常的其他方法的情況
繼承關(guān)系處理:
- 如果聲明的多個(gè)異常有繼承關(guān)系,編譯器會(huì)去重
- 例如同時(shí)聲明IOException和FileNotFoundException是允許的,但實(shí)際只需要聲明IOException即可,因?yàn)镕ileNotFoundException是其子類
運(yùn)行時(shí)異常:
- 雖然RuntimeException及其子類不需要在throws中聲明
- 但也可以選擇性地聲明,以提供給調(diào)用者更明確的提示
最佳實(shí)踐建議: 在聲明多個(gè)異常時(shí),應(yīng)該:1) 只聲明方法真正會(huì)拋出的異常;2) 避免聲明過(guò)于通用的異常類型;3) 保持異常的聲明與實(shí)際拋出的異常一致。
四、throw 和 throws 的聯(lián)系
一、同屬異常處理生態(tài)系統(tǒng)
throw和throws都是 Java 異常處理機(jī)制的關(guān)鍵構(gòu)成部分。Java 的異常處理旨在捕獲、報(bào)告并合理處理程序運(yùn)行時(shí)出現(xiàn)的錯(cuò)誤,保障程序的穩(wěn)定性與可靠性。throw負(fù)責(zé)在程序內(nèi)部主動(dòng)觸發(fā)異常事件,如同發(fā)現(xiàn)問(wèn)題后立即拉響警報(bào);throws則承擔(dān)著告知外界(調(diào)用該方法的其他代碼)可能出現(xiàn)異常風(fēng)險(xiǎn)的職責(zé),類似提前公示潛在的危險(xiǎn)區(qū)域。二者相互配合,從異常的產(chǎn)生到傳播,再到最終處理,貫穿異常處理的全過(guò)程,在不同階段發(fā)揮獨(dú)特作用,共同維護(hù)程序異常處理流程的順暢。
二、針對(duì)受檢異常的協(xié)同操作
受檢異常(Checked Exception)是 Java 異常體系中必須顯式處理的一類異常,如IOException、SQLException等。在處理受檢異常時(shí),throw和throws展現(xiàn)出緊密的協(xié)同關(guān)系。當(dāng)方法內(nèi)部執(zhí)行過(guò)程中檢測(cè)到受檢異常的發(fā)生條件,就需要使用throw創(chuàng)建并拋出具體的受檢異常對(duì)象,中斷當(dāng)前方法執(zhí)行流程。但由于受檢異常不能被 “默默忽略”,拋出異常的方法必須明確告知調(diào)用者該風(fēng)險(xiǎn),此時(shí)就需借助throws關(guān)鍵字在方法聲明處聲明可能拋出的受檢異常類型。例如,一個(gè)從文件讀取數(shù)據(jù)的方法readFile:
public String readFile(String filePath) throws FileNotFoundException {
FileReader reader = new FileReader(filePath);
// 讀取文件內(nèi)容的邏輯,若文件不存在,會(huì)觸發(fā)FileNotFoundException
if (!new File(filePath).exists()) {
throw new FileNotFoundException("文件 " + filePath + " 不存在");
}
// 后續(xù)讀取文件操作
}這里throw拋出FileNotFoundException,而throws聲明該異常,調(diào)用readFile方法的代碼就必須對(duì)該異常進(jìn)行處理,否則會(huì)導(dǎo)致編譯錯(cuò)誤。這種協(xié)同確保了受檢異常在整個(gè)程序中的規(guī)范處理,避免因異常未處理而引發(fā)的潛在運(yùn)行時(shí)錯(cuò)誤。
三、異常傳遞鏈條中的接力
在復(fù)雜的方法調(diào)用鏈中,throw和throws形成了異常傳遞的接力機(jī)制。當(dāng)方法 A 內(nèi)部使用throw拋出異常后,若 A 方法自身未使用try - catch塊捕獲處理該異常,就需要在方法聲明處使用throws聲明異常,將處理責(zé)任傳遞給調(diào)用 A 方法的上層方法 B。方法 B 面對(duì)傳遞過(guò)來(lái)的異常,同樣有兩種選擇:要么捕獲處理,要么繼續(xù)使用throws聲明,將異常進(jìn)一步傳遞給調(diào)用 B 的方法 C,以此類推,直到有方法對(duì)異常進(jìn)行捕獲處理或者異常傳遞到main方法。若main方法也不處理異常,最終異常會(huì)被 JVM 捕獲,導(dǎo)致程序終止并輸出異常信息。例如:
public class ExceptionChain {
public static void main(String[] args) {
try {
methodA();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void methodA() throws IOException {
methodB();
}
public static void methodB() throws IOException {
methodC();
}
public static void methodC() throws IOException {
throw new FileNotFoundException("文件未找到");
}
}在這個(gè)例子中,methodC使用throw拋出FileNotFoundException,通過(guò)methodB和methodA的throws聲明,異常最終傳遞到main方法,由main方法捕獲處理。throw和throws在這個(gè)鏈條中,一個(gè)負(fù)責(zé) “拋出異常啟動(dòng)傳遞”,一個(gè)負(fù)責(zé) “傳遞異常接力棒”,確保異常在整個(gè)調(diào)用鏈中有序傳播,便于在合適的層級(jí)進(jìn)行處理。
四、與自定義異常的結(jié)合運(yùn)用
自定義異常是開(kāi)發(fā)者根據(jù)業(yè)務(wù)需求創(chuàng)建的異常類型,用于更精準(zhǔn)地表示程序中出現(xiàn)的特定錯(cuò)誤情況。throw和throws在自定義異常的使用中也相互配合。首先,創(chuàng)建自定義異常類(通常繼承自Exception或其子類),然后在方法內(nèi)部,當(dāng)滿足特定業(yè)務(wù)異常條件時(shí),使用throw拋出該自定義異常對(duì)象。同時(shí),為了讓調(diào)用者知曉該方法可能拋出自定義異常,需在方法聲明處借助throws聲明自定義異常類型。例如,定義一個(gè)業(yè)務(wù)邏輯中賬戶余額不足的自定義異常:
class InsufficientBalanceException extends Exception {
public InsufficientBalanceException(String message) {
super(message);
}
}
public class BankAccount {
private double balance;
public BankAccount(double initialBalance) {
this.balance = initialBalance;
}
public void withdraw(double amount) throws InsufficientBalanceException {
if (amount > balance) {
throw new InsufficientBalanceException("賬戶余額不足,當(dāng)前余額為 " + balance + ",取款金額為 " + amount);
}
balance -= amount;
}
}在withdraw方法中,throw拋出InsufficientBalanceException自定義異常,throws聲明該異常,調(diào)用withdraw方法的代碼就需要針對(duì)該自定義異常進(jìn)行處理,使業(yè)務(wù)異常在程序中得到妥善的表達(dá)與處理。
五、異常類型匹配與多態(tài)性體現(xiàn)
在 Java 的異常處理體系中,異常類型之間存在繼承關(guān)系,這使得throw和throws在處理異常時(shí)體現(xiàn)出多態(tài)性特點(diǎn)。當(dāng)使用throw拋出一個(gè)具體異常對(duì)象時(shí),它可以是某個(gè)異常類的實(shí)例,也可以是該異常類子類的實(shí)例。而throws聲明的異常類型可以是實(shí)際拋出異常的父類。例如,方法內(nèi)部可能拋出FileNotFoundException(它是IOException的子類),那么在方法聲明處既可以使用throws FileNotFoundException,也可以使用throws IOException聲明。這種基于異常類型繼承的多態(tài)性,讓throw和throws在異常處理中更加靈活,調(diào)用者可以根據(jù)更寬泛的異常類型進(jìn)行統(tǒng)一處理,同時(shí)又不失異常的具體性。例如:
public void readData() throws IOException {
try {
// 讀取數(shù)據(jù)操作,可能拋出FileNotFoundException
FileReader reader = new FileReader("data.txt");
} catch (FileNotFoundException e) {
// 捕獲具體異常
throw e; // 重新拋出異常,可由調(diào)用者根據(jù)聲明的IOException統(tǒng)一處理
}
}這里readData方法聲明拋出IOException,內(nèi)部實(shí)際拋出FileNotFoundException,通過(guò)throw和throws對(duì)異常類型的匹配與多態(tài)運(yùn)用,實(shí)現(xiàn)了異常處理的靈活性與層次性。
綜上,throw和throws在 Java 異常處理中相互依存、協(xié)同工作,從異常的拋出、傳遞到處理,在各個(gè)環(huán)節(jié)緊密配合,為開(kāi)發(fā)者提供了強(qiáng)大且靈活的異常處理工具,助力構(gòu)建健壯、可靠的 Java 程序。
五、注意事項(xiàng)
1.使用 throw 時(shí)的注意事項(xiàng)
throw后面必須跟一個(gè)具體的異常對(duì)象實(shí)例,不能直接使用異常類型。例如:
// 錯(cuò)誤寫(xiě)法
throw IllegalArgumentException;
// 正確寫(xiě)法
throw new IllegalArgumentException("參數(shù)不能為null");拋出異常后會(huì)立即中斷當(dāng)前方法的執(zhí)行流程,控制權(quán)會(huì)轉(zhuǎn)移到最近的異常處理塊。因此,在throw語(yǔ)句后面放置的任何代碼都不會(huì)被執(zhí)行:
public void processValue(int value) {
if (value < 0) {
throw new IllegalArgumentException("數(shù)值不能為負(fù)數(shù)");
// 下面這行代碼永遠(yuǎn)不會(huì)執(zhí)行
System.out.println("繼續(xù)處理...");
}
// 其他處理邏輯
}
對(duì)于checked異常的處理要求:
- 必須在方法內(nèi)部使用try-catch塊捕獲處理
- 或者在方法簽名中使用throws聲明拋出 例如:
// 方式1:捕獲處理
public void readFile() {
try {
// 可能拋出IOException的代碼
} catch (IOException e) {
// 處理異常
}
}
// 方式2:聲明拋出
public void readFile() throws IOException {
// 可能拋出IOException的代碼
}2.用 throws 時(shí)的注意事項(xiàng)
throws聲明的異常類型必須與方法內(nèi)部實(shí)際可能拋出的異常匹配:
// 錯(cuò)誤:聲明了不相關(guān)的異常
public void process() throws SQLException {
// 這個(gè)方法實(shí)際上不會(huì)拋出SQLException
throw new IOException(); // 編譯錯(cuò)誤
}
// 正確
public void process() throws IOException {
throw new IOException();
}調(diào)用聲明了異常的方法時(shí)的處理方式:
// 情況1:捕獲處理
public void caller() {
try {
methodThrowsException();
} catch (IOException e) {
// 處理異常
}
}
// 情況2:繼續(xù)聲明
public void caller() throws IOException {
methodThrowsException();
}方法重寫(xiě)時(shí)的異常聲明規(guī)則:
class Parent {
public void demo() throws IOException {
// 父類方法
}
}
class Child extends Parent {
// 正確:不拋出異常
@Override
public void demo() {
// 子類實(shí)現(xiàn)
}
// 正確:拋出更具體的異常
@Override
public void demo() throws FileNotFoundException {
// 子類實(shí)現(xiàn)
}
// 錯(cuò)誤:拋出更寬泛的異常
@Override
public void demo() throws Exception {
// 編譯錯(cuò)誤
}
}到此這篇關(guān)于Java 中 throw 和 throws 的全面解析的文章就介紹到這了,更多相關(guān)Java 中 throw 和 throws內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot時(shí)區(qū)設(shè)置的幾個(gè)方面小結(jié)
在日常開(kāi)發(fā)中,時(shí)區(qū)問(wèn)題常常像潛伏的地雷,一旦跨地區(qū)、跨服務(wù)器或跨系統(tǒng)部署,就可能出現(xiàn)日志錯(cuò)亂、數(shù)據(jù)庫(kù)時(shí)間偏移、定時(shí)任務(wù)不準(zhǔn)等問(wèn)題,本文就來(lái)介紹一下SpringBoot時(shí)區(qū)設(shè)置,感興趣的可以了解一下2026-02-02
Java使用Redisson分布式鎖實(shí)現(xiàn)原理
Redisson分布式鎖 之前的基于注解的鎖有一種鎖是基本redis的分布式鎖,這篇文章主要介紹了Java使用Redisson分布式鎖實(shí)現(xiàn)原理,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2018-10-10
SpringBoot+Vue.js實(shí)現(xiàn)前后端分離的文件上傳功能
這篇文章主要介紹了SpringBoot+Vue.js實(shí)現(xiàn)前后端分離的文件上傳功能,需要的朋友可以參考下2018-06-06
基于Lucene的Java搜索服務(wù)器Elasticsearch安裝使用教程
Elasticsearch也是用Java開(kāi)發(fā)的,并作為Apache許可條款下的開(kāi)放源碼發(fā)布,能夠做到實(shí)時(shí)搜索,且穩(wěn)定、可靠、快速,安裝使用方便,這里我們就來(lái)看一下基于Lucene的Java搜索服務(wù)器Elasticsearch安裝使用教程:2016-06-06
IDEA中右側(cè)沒(méi)有顯示Maven問(wèn)題及解決
解決IDEA中Maven工具窗口不顯示的問(wèn)題,包括手動(dòng)重新加載Maven項(xiàng)目、啟用Maven插件、手動(dòng)加載Maven項(xiàng)目、重置IDEA配置和檢查IDEA版本和Maven配置2026-02-02

