文章目录
- 1. 异常概述
- 2. 异常的继承结构
- 3. 自定义异常
- 4. 异常的处理
- 5. 异常的使用
- 6. finally语句块
- 7. 方法覆盖与异常
1. 异常概述
什么是异常
①什么是异常?有什么用?
1.Java中的异常是指程序运行时出现了错误或异常情况,导致程序无法继续正常执行的现象。例如,数组下标越界,空指针异常,类型转换异常等都属于异常情况,
2、 Java提供了异常处理机制,即在程序中对可能出现的异常情况进行捕捉和处理。异常机制可以帮助程序员更好地管理程序的错误和异常情况,避免程序崩溃或出现不可预测的行为。
3.没有异常机制的话,程序中就可能会出现一些难以调试和预测的异常行为,可能导致程序崩溃,甚至可能造成数据损失或损害用户利益。因此,异常机制是一项非常重要的功能,是编写可靠程序的基础。
异常的存在形式
一句话总结, Java当中异常以类和对象的方式存在, 让我们看下面这一段代码
public class Mainpublic static void main(String[] args){String s = null;System.out.print(s.toString());}}
}
上述代码明显发生了一个著名的异常 – 空指针异常
那这个异常的底层到底是如何发生的呢, 其实质上是new 了一个空指针异常的对象然后抛出
public class Mainpublic static void main(String[] args){//由于这个运行时异常没有被捕获, 所以直接导致了JVM的终止运行throw new NullPointerException();}}
}
2. 异常的继承结构
异常的继承结构详解
①所有的异常和错误都是可抛出的,都继承了Throwable类。
②Error是无法处理的,出现后只有一个结果:JVM终止。
③Exception是可以处理的。
④Exception的分类
- 所有的RuntimeException的子类:运行时异常/未检查异常(UncheckedException)/非受控异常
- Exception的子类(除RuntimeException之外):编译时异常/检查异常(CheckedException)/受控异常
⑤编译时异常和运行时异常区别:
- 编译时异常特点;在编详阶段必须提前处理,如果不处理编详器报错。
运行时异常特点;在编译阶段可以选择处理,也可以不处理,没有硬性要求。- 编译时异常一般是由外部环境或外在条件引起的,如网络故障,磁盘空间不足。文件找不到等
运行时异常一般是由程序员的错误引起的,并且不需要强制进行异常处理
为什么有编译时异常的这个说法
注意:编译时异常并不是在编译阶段发生的异常,所有的异常发生都是在运行阶段的,因为每个异常发生都是会new异常对象的,new异常对象只能在运行阶段完成。那为什么叫做编译时异常呢?这是因为这种异常必须在编译阶段提前预处理,如果不处理编译器报错,因此而得名编译时异常。
图解异常的继承结构
下面我们用starUML工具画一下异常继承体系的简图
(其实异常的机构体系十分庞大, 我们的这个图只是举了几个类作为示例)
3. 自定义异常
自定义异常的方法就两步
- 编写一个异常类继承Exception或者是RuntimeException(原则就是判断是程序员的错还是其他)
- 给两个构造方法, 一个无参的, 一个带有一个String参数, 调用父类的构造方法
这个方法的灵感其实来源于源码, 上面我们的UML类继承图也指出, 其实异常继承机构中真正起作用的是Throwalbe方法
下面我们给出例子
/*** 年龄非法异常类*/
public class IllegalAgeException extends Exception{//无参数的构造方法public IllegalAgeException(){super();}//带有一个参数String的构造方法public IllegalAgeException(String message){super(message);}
}
/*** 名字非法异常类*/
public class IllegalNameException extends Exception{//无参数的构造方法public IllegalNameException() {super();}//带有一个参数String的构造方法public IllegalNameException(String message) {super(message);}
}
完成下面的业务逻辑
自定义异常
①定义两个编译时异常:
IlegalNameException:无效名字异常
IllegallAgeException:无效年龄异常
②完成这样的需求:
1.编写一个用户注册的方法,该方法接收两个参数,一个是用户名,一个是年龄。如果用户名长度在6-12]位,并且年龄大于18岁时,输出用户注册成功。
2.如果用户名长度不是[6-12)位时,让程序出现异常,让llegalNameException异常发生!如果年龄小于18岁时,让程序出现异常,让llegalAgeException异常发生!
下面是我们的实现的代码, 我们先通过throws把异常都抛出给上级
import powernode.exception.IllegalAgeException;
import powernode.exception.IllegalNameException;import java.util.Scanner;/*** 测试的主程序* 本次测试中我们的异常机制强行抛给了Main函数来处理* 而Main函数又上抛给虚拟机JVM进行处理, 而JVM的处理机制就是直接断掉测试程序*/
class ExceptionTest{public static void main(String[] args) throws IllegalNameException, IllegalAgeException {Scanner sc = new Scanner(System.in);System.out.println("请输入名字 : ");String name = sc.next();System.out.println("请输入年龄 : ");int age = sc.nextInt();UserService userService = new UserService();userService.register(name,age);}
}/*** 下面是用户层, 用于接收用户的处理请求*/
public class UserService {public void register(String name, int age) throws IllegalAgeException, IllegalNameException {System.out.println("当前用户正在注册...");Sql sql = new Sql();sql.save(name,age);System.out.println("注册任务已完成...");}
}/*** 下面是数据库层, 用来连接数据库, 在数据库中保存相关的用户信息*/
class Sql {public void save(String name, int age) throws IllegalAgeException, IllegalNameException {//在这里对传入的参数进行处理操作if (name.length() < 6 || name.length() > 12) {throw new IllegalNameException();}if (age < 18) {throw new IllegalAgeException();}System.out.println("您的个人信息在数据库中保存成功...");}
}
4. 异常的处理
- 声明异常: 这是一种推卸责任的处理方式, 在方法定义的时候通过throws关键字声明异常, 告诉使用者我们的这个位置可能会出现异常, 这种处理异常的态度是, 如果发生了异常我们就上抛给调用者去解决
- 捕获异常: 真正意义上的去处理异常, 通过try-catch的方法进行抓住, 如果其他方法调用这个方法的时候, 如果在方法内部已经抓住了, 那么对于调用者来说是不知道异常发生的
- 对于运行时异常来说, 我们没有必要进行强制的处理, JVM会自动的将异常向上抛出(throws处理), 所以我们之前发生空指针异常的时候, JVM得到了异常信息之后就会直接的关掉程序
- 对于编译时异常来说, 就需要我们手动的处理, 我们可以选择throws把问题抛给上级, 也可以选择try-catch自己捕获住
- 对于异常的处理态度应该是声明跟捕获联合使用
- 如何进行选择: 如果异常的发生是需要调用者知道的那我们就进行throws抛出, 否则采用捕获
throws方法的具体语法
try-catch的基本语法
上面的逻辑的代码采用处理的方式可以改为下面这样
import java.util.Scanner;/*** 测试的主程序* 本次测试中我们的异常机制强行抛给了Main函数来处理, 而Main函数又上抛给虚拟机JVM进行处理, 而JVM的处理机制就是直接断掉测试程序*/
class ExceptionTest{public static void main(String[] args){Scanner sc = new Scanner(System.in);System.out.println("请输入名字 : ");String name = sc.next();System.out.println("请输入年龄 : ");int age = sc.nextInt();UserService userService = new UserService();try{userService.register(name,age);}catch(IllegalAgeException e) {e.printStackTrace();}catch(IllegalNameException e){e.printStackTrace();}}
}
5. 异常的使用
我们比较常用的其实就下面的两种方法
- getMessage()
获取到最初创建异常的时候的传递出去的message(也就是那个字符串)
其实我们分析源码就可以了解到, 当时我们调用super(message)的时候, 我们的message其实给到了throwable中的一个属性字符串, 现在我们调用getMessage()的时候实质上是返回了该字符串- printStackTrace();
打印的是异常在堆栈中的信息(也就是我们的红色字体), 该方法返回的时候是符合栈的数据结构的, 所以我们查看异常的时候应该从顶部查看(栈顶)
6. finally语句块
- finally语句块的代码是一定会执行的
- finally语句块不可以进行单独的使用, 一定要配合try块一起使用
通常的使用结构是 try - finally / try - catch - finally- finally语句块通常完成资源的释放(因为一定会执行)
思考下面的案例的执行结果
public static int m1(){try{System.out.println("try语句块");return 1;}finally{System.out.println("finally语句块");}
}答案是try语句块finally语句块
public static int m1(){try{System.out.println("try语句块");System.exit(0);}finally{System.out.println("finally语句块");}
}答案是try语句块
也就是说只有System.exit(0)方法(终止JVM的运行)才能停掉finally语句块的运行
7. 方法覆盖与异常
就一句话, 抛出的异常不可以变多可以变少
如果子类抛出了一个父类抛出异常的父类, 那么这种情况也会编译报错