异常的处理机制
当程序出现意味的情况时,系统会自动生成一个异常对象来通知程序
- 程序中,可以使用特定的语句捕获异常对象,读取对象中的信息,进而做出处理;
- 程序中,可以使用特定的语句抛出异常对象,将这个异常交给程序调用者处理
- 可以在捕获异常后,做出尽可能的处理,之后再向外抛出,将剩下的部分,交给程序的调用者处理
异常的继承体系
-
所有的非常的情况被分为两类 错误和异常其中 错误代表虚拟机相关问题,一般无法处理,也无需处理
-
所有的异常(exception)被分为两类 runtimeException,代表运行时异常,程序可以显示处理这种异常,也可以不处理,而是交给顶层调用者统一处理;
-
非运行时异常(Checked异常)程序必须显示地处理这种异常,否则在编译阶段会发生错误,导致程序无法编译通过编译
捕获异常
- try catch 语句
try{
业务逻辑
}catch(Exception e){
异常处理代码
}
- 无论哪行代码发生异常,系统都会生成一个异常对象,这与try …catch…语句没有关系
- 若程序没有对这个异常做任何处理,则程序在此退出
- 创建异常对象后,jvm会寻找可以处理它的catch快,并将异常对象交给这个catch去处理
- 括号里的参数是Exception类型,是所有异常的父类 所以他能处理所有的异常情况。
访问异常信息
- String getMessage() 返回该异常的详细消息字符串
StackTraceElement[] getStackTrace() 返回该异常的跟踪栈信息
void printStackTrace() 将该异常的跟踪栈信息打印到标准输出设备
几个常见的异常类型演示
public static void main(String[] args) {
// process1(args);
process2(args);
}
// 几个常见的异常类型
// ArrayIndexOutOfBoundsException 数组索引越界异常
// NumberFormatException 数字格式异常
// ArithmeticException 算术异常
// 处理异常原则 先小异常再大异常
public static void process1(String[] args){
try {
int m = Integer.parseInt(args[0]);
int n = Integer.parseInt(args[1]);
System.out.println("m / n =" + (m / n));
}catch (Exception e){
System.out.println("##############################3");
e.printStackTrace();
System.out.println("##############################3");
System.out.println(e.getMessage());
System.out.println("##############################3");
for (StackTraceElement element: e.getStackTrace()){
}
}
}
public static void process2(String[] args) {
try {
int m = Integer.parseInt(args[0]);
int n = Integer.parseInt(args[1]);
System.out.println("m / n =" + (m / n));
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("数组小标越界“;" + e.getMessage());
}catch (NumberFormatException e ){
System.out.println("数字格式错误“;" + e.getMessage());
}catch (ArithmeticException e){
System.out.println("数学运算失败2“;" + e.getMessage());
}catch (Exception e){
System.out.println("未知异常“;" + e.getMessage());
}
}
}
回收资源
来想想把回收资源的代码放在那里耶
- 放在 try的后面?显然不行为什么 因为放在try的后面 要是前面出现了异常 就直接去了catch里面了 回收资源代码就执行不到了
- 放在catch里面的后面? try里面没有异常就捕获不到 就不会执行catch
- 直接不放在try — catch 里面 放在 catch的后面 ? 如果try 有异常 不处理的话 直接打印异常 中断程序 也不行
- 所以为了 解决这一现象 增加了一个finally代码块
所以完整的是 try…catch…finally
finally 注意事项
- finally块中的代码 总是会被执行
- try 是必须的,catch ,finally 是可选的
- catch finally 这两个至少要出现一个
- finally 最多出现一次 但是他必须位于 try catch 的最后面
代码演示
public static void main(String[] args) {
// process1(args);
process2(args);
}
// 几个常见的异常类型
// ArrayIndexOutOfBoundsException 数组索引越界异常
// NumberFormatException 数字格式异常
// ArithmeticException 算术异常
// 处理异常原则 先小异常再大异常
public static void process1(String[] args){
try {
int m = Integer.parseInt(args[0]);
int n = Integer.parseInt(args[1]);
System.out.println("m / n =" + (m / n));
}catch (Exception e){
System.out.println("##############################3");
e.printStackTrace();
System.out.println("##############################3");
System.out.println(e.getMessage());
System.out.println("##############################3");
for (StackTraceElement element: e.getStackTrace()){
}
}
}
public static void process2(String[] args) {
try {
int m = Integer.parseInt(args[0]);
int n = Integer.parseInt(args[1]);
System.out.println("m / n =" + (m / n));
System.out.println("我被执行了");
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("数组小标越界“;" + e.getMessage());
}catch (NumberFormatException e ){
System.out.println("数字格式错误“;" + e.getMessage());
}catch (ArithmeticException e){
System.out.println("数学运算失败2“;" + e.getMessage());
}catch (Exception e){
System.out.println("未知异常“;" + e.getMessage());
}finally {
try {//try catch 可以被嵌套
System.out.println("FINALLY 我被执行了");
int m = Integer.parseInt(args[0]);
}catch (ArrayIndexOutOfBoundsException e){
System.out.println(e.getMessage());
}
}
System.out.println("出来了");
}
}
抛出异常
- 声明抛出异常
方法() 后面接 throws 接异常类型
-
throws 语句用于标识某方法可以抛出的异常,它位于方法签名之后
-
throws 语句声明抛出异常后,程序中就无需使用try语句捕获该异常了;
-
再重写时,子类方法声明 抛出异常类型不能比父类方法声明抛出的异常类型大
-
作用是 用于在程序中主动抛出一个异常
-
throw 语句抛出的不是异常类型,而是一个异常实例
-
对于主动抛出的异常,也可以采用try块捕获,或者采用throw语句向外抛出由调用捕获或者处理 或者在抛出去
-
thows用在方法声明的时候,表示这个方法可能会抛出这个异常,可以声明多种异常,由方法的调用者捕获
-
thow用在方法体内,用于抛出一个异常实例,由方法内部的语句进行处理。
public static void main(String[] args) {
process1(args);
}
public static void process1(String[] args)
throws ArrayIndexOutOfBoundsException,NumberFormatException,ArithmeticException{
int m = Integer.parseInt(args[0]);
int n = Integer.parseInt(args[1]);
System.out.println("m / n =" + (m / n));
}
}
自定义异常
- 若定义Checked异常,则继承于Exception
- 若定义Runtime异常,则继承于RuntimeException
- 自定义的异常类,通常需要提供如下构造方法;
public SomeException(){};
public SomeException(String message){};
public SomeException(String message ,Throwable cause){};
代码演示
public static void main(String[] args) {
try {
String s = inputUsername();
System.out.println(s);
} catch (UsernameException e) {
System.out.println(e.getMessage());
}
}
public static String inputUsername() throws UsernameException {
System.out.println("请输入账号:");
try (
Scanner scanner = new Scanner(in);
) {
String s = scanner.nextLine();
if (!s.matches("^\\w{6,20}$")) {//正则表达式
throw new UsernameException("账号格式错误!");
}
return s;
} catch (RuntimeException e) {
throw new UsernameException("输入账号失败",e);
}
}
}
//自定义异常
class UsernameException extends Exception{
public UsernameException() {
super();
}
public UsernameException(String message) {
super(message);
}
public UsernameException(String message, Throwable cause) {
super(message, cause);
}
}
异常追踪栈
- 程序运行时,经常会发生一系列方法的调用,从而形成方法调用栈
- 异常机制会导致异常在这些方法 之间传播,而异常传播的顺序与方法的调用相反
- 异常从发生异常的方法向外传播,首先传给该方法的调用者,再传给上层调用者
- 最终传给main方法,若依然没有得到处理,则jvm会终止程序,并打印异常追踪栈信息;
异常处理的原则
- 不要过度使用异常 不要用异常代理错误处理代码,不要用异常处理代替流程控制语句
- 不要忽略捕获异常 对于捕获到的异常 ,要进行合适的修复,对于不能处理的部分,应该抛出新的异常
- 不要直接捕获所有的异常 应该不同异常做出有针对性的处理,而捕获所有的异常容易压制(丢失)异常
- 不要使用过于庞大的try代码块 庞大的代码块会导致业务过于复杂,不利于分析异常的原因,也不利于程序的阅读和维护!!!!!