思维导图:
5.4 抛出异常笔记
概述
在Java中,如果某些异常不需要立即处理,我们可以选择将它们抛出,这样异常的处理责任就转移给了该类的调用者。Java通过throws
和throw
关键字来实现异常的抛出。
5.4.1 使用throws
关键字
当一个方法可能会抛出某种异常,但不在当前方法中处理时,可以在方法声明时使用throws
关键字后跟异常类型。这样做的结果是,调用这个方法的代码必须处理这些异常,否则程序将无法编译通过。
语法格式
修饰符 返回值类型 方法名(参数列表) throws 异常类型1, 异常类型2, ... {
// 方法体
}
throws
关键字位于方法签名的末尾。- 方法可以声明抛出一个或多个异常。
示例更改
假设有一个divide方法,原始版本未声明异常,现在我们添加throws
声明:
// 原始版本
public static int divide(int x, int y) {
return x / y;
}
// 修改后的版本,声明异常
public static int divide(int x, int y) throws Exception {
return x / y;
}
在调用divide
方法的主方法中,即使第二个参数不为零,也必须处理可能的异常,否则会出现编译错误。
编译错误案例
public class Example04 {
public static void main(String[] args) {
int result = divide(4, 2); // 编译错误:未处理的异常
System.out.println(result);
}
public static int divide(int x, int y) throws Exception {
return x / y;
}
}
异常处理
为了解决编译错误,我们需要在调用可能抛出异常的方法时使用try...catch
语句。
文件5-5更改示例
public class Example05 {
public static void main(String[] args) {
try {
int result = divide(4, 2);
System.out.println(result);
} catch (Exception e) {
e.printStackTrace();
}
}
public static int divide(int x, int y) throws Exception {
return x / y;
}
}
- 使用
try
块调用divide
方法。 - 使用
catch
块捕获并处理异常。
程序运行正常,没有编译错误,如果divide
方法抛出异常,它将被catch
块捕获。
继续抛出异常 (throws
关键字的连锁使用)
-
重抛异常:当一个方法中使用了
throws
关键字声明异常,调用者可以选择处理这个异常,或者也可以选择再次使用throws
关键字将异常声明抛出。 -
编译通过,运行时错误:即使方法调用者选择不处理而是继续抛出异常,程序仍然能够编译通过。但是,如果最终没有任何一个调用者处理了这个异常,当异常发生时,程序运行会被中断。
-
例子解析:
Example06.java
中的main
方法通过声明throws Exception
,将divide
方法可能抛出的异常继续抛出。- 在
main
方法中调用了divide(4, 0)
,显然,这里会产生一个除以零的异常(ArithmeticException
)。 - 因为
main
方法并没有处理这个异常,所以异常向上传播到了程序的顶层,导致了程序运行的中止。
-
关键代码:
public static void main(String[] args) throws Exception { int result = divide(4, 0); // 这里传入的参数会导致除零异常 System.out.println(result); } public static int divide(int x, int y) throws Exception { int result = x / y; // 这里可能抛出除零异常 return result; }
-
程序输出:
- 控制台显示了异常的类型(
ArithmeticException
),这是一个运行时异常。 - 异常的追踪信息指向
divide
方法内发生除零操作的地方,以及调用divide
方法的main
方法内的位置。
- 控制台显示了异常的类型(
-
异常处理的重要性:
- 本例强调了处理异常的必要性。仅仅使用
throws
关键字声明异常而不进行任何处理,会导致异常在程序中向上级调用者传递,如果所有上级都不处理,最终会由JVM捕获,并且可能导致程序异常终止。
- 本例强调了处理异常的必要性。仅仅使用
-
最佳实践:
- 应该在能够恰当处理异常的地方捕获和处理它们,以防止异常导致整个程序的崩溃。
- 如果异常可以由上层逻辑恰当处理,则应使用
throws
关键字传递它们,否则应在当前层级捕获并处理。
小结
通过使用throws
关键字,我们将异常处理的责任传递给了方法的调用者。调用者必须使用try...catch
来处理异常,否则程序无法编译。这提高了代码的安全性,使得异常处理变得更加清晰和严格。
我的理解:
理解这一节的关键在于掌握异常处理的概念和Java中实现异常处理的两个关键字:throws
和throw
。
异常处理基础
在Java程序中,异常是在运行时发生的一种异常事件,它打断了正常的指令流程。异常处理是一种机制,用于捕捉运行时的错误,并提供恢复或至少允许程序优雅终止的方式。
throws
关键字
- 作用:
throws
关键字用于方法签名中,用来声明这个方法可能会抛出的异常类型。一旦声明,任何调用这个方法的代码都必须处理这些异常,不管是通过try-catch
来捕获和处理,还是通过在自己的方法签名中再次使用throws
将异常传递出去。 - 目的:这使得异常可控和可预测,因为调用者在编译期就被迫考虑到异常处理,从而增加代码的健壮性。
throw
关键字
- 作用:
throw
关键字则是用来在方法体内部显式抛出一个异常实例。它通常是在你检测到某种错误条件时使用,手动抛出一个异常。 - 示例:如果你在一个方法中检测到一个错误(比如一个不合法的参数),你可以创建一个异常对象并用
throw
关键字抛出。
举例理解
假设有一个方法,这个方法在执行过程中可能会遇到一个错误条件,这时候有两种选择:
- 立即处理:使用
try-catch
块捕获这个异常,并且立即在方法内部处理。 - 声明抛出:如果你不想或不能处理这个异常,你可以在方法签名中使用
throws
关键字声明这个异常,把异常处理的责任传递给调用这个方法的代码。
throws
和throw
的关系
throw
是用来抛出一个具体的异常实例的。throws
是用来声明一个方法可能会抛出的所有异常的,它是告诉调用者这个方法可能会抛出这些异常,并且调用者应该准备好处理这些异常。
实际应用
在实际开发中,你可能会调用一些第三方库的方法,这些方法在其方法签名中声明了它们可能抛出的异常。作为调用者,你需要决定如何处理这些异常——要么自己立即处理它们,要么继续声明抛出这些异常,让你的方法的调用者去处理。
通过throws
关键字可以提高代码的可读性和可维护性,因为它强制程序员处理可能发生的异常,而不是忽略它们,这可以避免许多潜在的运行时错误和不稳定的状态。
5.4.2 throw
关键字的使用
-
概念区分:
throw
与throws
关键字功能不同,throw
用于方法体内部,抛出具体的异常实例。throws
关键字用于方法声明上,表示该方法可能会抛出的异常类型。
-
throw
使用场景:- 用于方法体内抛出单一异常实例。
throw new ExceptionType("Error message");
-
处理
throw
抛出的异常:- 必须在方法内部用
try...catch
捕获,或者在方法声明上使用throws
继续声明抛出。 RuntimeException
及其子类异常(即非受检异常)不强制要求使用throws
声明或try...catch
捕获。
- 必须在方法内部用
-
具体用例分析:
- 编译时异常(Checked Exception):
- 使用
throw
抛出时,必须在方法声明上使用throws
或者在方法内部使用try...catch
处理。
- 使用
- 运行时异常(Runtime Exception):
- 抛出时可以不用在方法声明上使用
throws
,且可以选择是否使用try...catch
捕获。
- 抛出时可以不用在方法声明上使用
- 编译时异常(Checked Exception):
-
案例讲解:(
Example07.java
)printAge()
方法通过throw
关键字抛出异常。- 在输入非法年龄(如负数)时抛出异常,指定异常信息。
- 方法声明使用
throws Exception
表明可能抛出异常。 main()
方法中使用try...catch
捕获并处理printAge()
抛出的异常。
-
关键代码片段:
public static void printAge(int age) throws Exception { if(age <= 0) { // 当输入年龄不合逻辑时,通过throw关键字抛出异常 throw new Exception("输入的年龄有误,必须是正整数!"); } else { System.out.println("此人年龄为:" + age); } } public static void main(String[] args) { int age = -1; try { // 尝试输出年龄 printAge(age); } catch(Exception e) { // 捕获并处理异常 System.out.println("捕获的异常信息为:" + e.getMessage()); } }
-
运行结果与分析:
- 控制台打印异常信息:“输入的年龄有误,必须是正整数!”
- 程序通过捕获异常,避免了因异常而直接终止,保证了业务逻辑的正确性和程序的稳定运行。
-
重要提示:
- 使用
throw
抛出逻辑性异常时,合理地捕获和处理异常至关重要,可以避免程序的不稳定和意外中止。 throw
也可以用于抛出系统自动识别的异常,例如NullPointerException
、IllegalArgumentException
等。
- 使用
我的理解:
这一节的核心概念是 throw
和 throws
关键字在Java异常处理中的使用。
-
throw
关键字:- 用途:在方法体内部主动抛出一个具体的异常实例。
- 语法:
throw new SomeException("Error message");
- 场景:当程序运行到特定条件,需要通知方法的调用者该条件出现问题时使用。
- 效果:一旦执行了
throw
语句,它后面的代码不会继续执行,异常会被立即抛出。 - 处理:抛出的异常必须被捕获(
try...catch
)或者被声明抛出(throws
)。
-
throws
关键字:- 用途:用在方法签名上,声明该方法可能会抛出的异常类型。
- 语法:
public void someMethod() throws SomeException
- 场景:当方法内部抛出的异常不打算在当前方法内解决,而是想让方法的调用者去处理时使用。
- 效果:允许方法在没有捕获异常的情况下,将异常传递出去,最终由方法的调用者或者JVM捕获处理。
理解这些概念的关键点在于:
-
异常抛出(Throwing an Exception):
- 你可以创建一个异常对象并用
throw
关键字抛出。 - 通常情况下,这是在方法体中,作为某种条件发生时的响应(例如,检测到非法参数值)。
- 你可以创建一个异常对象并用
-
异常声明(Declaring Exceptions):
- 使用
throws
关键字在方法签名中声明异常,告诉方法的调用者这个方法可能会抛出异常,调用者需要准备好处理这些异常。
- 使用
-
异常捕获(Catching an Exception):
- 通过
try...catch
块捕获和处理方法中抛出的异常。 - 如果
throw
抛出的是运行时异常(RuntimeException
的子类),你可以选择不在方法签名上使用throws
,因为它们是非检查型异常。
- 通过
-
传递异常(Propagating an Exception):
- 如果你不想或不能处理某个异常,可以选择继续使用
throws
在方法签名中声明,让这个异常进一步传递给上层方法的调用者。
- 如果你不想或不能处理某个异常,可以选择继续使用
通过实际代码示例和运行结果,这一节展示了在特定条件下使用 throw
抛出异常,并通过 try...catch
捕获异常以及使用 throws
将异常声明给调用者的整个过程。通过这些示例,你可以更深入地理解异常在Java中的工作方式和异常处理流程。
总结:
重点:
-
throw
关键字用法:- 在方法体内部主动抛出一个异常实例。
- 每次只能抛出一个异常实例。
-
throws
关键字用法:- 在方法声明时用来指示该方法可能抛出的异常类型。
- 一个方法可以声明抛出多个异常类型。
-
异常类型:
- 分为检查型异常(checked exceptions)和非检查型异常(unchecked exceptions,即运行时异常)。
- 非检查型异常不强制要求显式处理。
-
异常处理:
- 抛出的异常可以被
try...catch
捕获并处理。 - 如果不处理,则必须在方法签名中使用
throws
关键字声明。
- 抛出的异常可以被
难点:
-
何时使用
throw
vsthrows
:throw
是用来实际抛出一个异常实例。throws
用于声明一个方法可能会抛出什么类型的异常。
-
异常的传递:
- 理解异常如何在调用堆栈中传递。
- 理解异常传递对程序控制流的影响。
-
异常的处理策略:
- 什么时候捕获异常,什么时候传递异常。
- 如何处理多个异常,以及如何处理异常链。
易错点:
-
忘记捕获或声明异常:
- 忘记用
try...catch
处理throw
语句可能会导致编译错误(对于检查型异常)。 - 忘记在方法签名中用
throws
声明异常也会导致编译错误。
- 忘记用
-
错误地处理异常:
- 捕获了异常却没有做任何处理或者只是简单地打印堆栈跟踪,这可能会掩盖问题的真相。
- 在异常处理中不当地使用
System.exit()
可能会导致程序意外终止。
-
混淆异常类型:
- 将非检查型异常声明在
throws
子句中是不必要的,这可能会引起混淆。 - 将检查型异常误当作非检查型异常处理,不在方法签名中声明,这将导致编译错误。
- 将非检查型异常声明在
通过掌握这些重点、难点和易错点,你可以更有效地理解和应用Java中的异常处理机制。在编写和调试Java程序时,这些知识点将帮助你避免常见的错误,并编写出更稳健的异常处理代码。