1、异常Exception概述:
1)、异常的概念:
现实生活中万物在发展和变化会出现各种各样不正常的现象。
例如:人的成长过程中会生病。 实际工作中,遇到的情况不可能是非常完美的。
比如:你写的某个模块,用户输入不一定符合 你的要求、你的程序要打开某个文件,这个文件可能不存在或者文件格式不对,你要读取数据 库的数据,数据可能是空的等。 我们的程序再跑着,内存或硬盘可能满了等等。 软件程序在运行过程中,非常可能遇到刚刚提到的这些异常问题,我们叫异常,英文是: Exception , 意思是例外。 这些,例外情况,或者叫异常,怎么让我们写的程序做出合理的处 理,安全的退出,而不至于程序崩溃。
2)、实际开发中异常的理解:
实际开发中,异常从面向对象的角度考虑也是一类事物,我们可以向上抽取为异常类。这个异常类可以对一些不正常的现象进行描述,并封装为对象。
2、异常体系:
JDK 中定义了很多异常类,这些类对应了各种各样可能出现的异常事件,所有异常对象都是 派生于 Throwable 类的一个实例。 如果内置的异常类不能够满足需要,还可以创建自己的异 常类。 Thorwable 类(表示可抛出)是所有异常和错误的超类,两个直接子类为 Error ( 错误)和 Exception ( 异常)。
先来看看 java 中异常的体系结构图解:
1)、Error类:
Error是 java 所有错误类的父类,描述了 java 运行时系统内部错误和资源耗尽错误。这 类错误是我们无法控制的,同时也是非常罕见的错误,表明系统 JVM 已经处于不可恢复的崩 溃状态中,它是由 JVM 产生和抛出的,比如 OutOfMemoryError 、 ThreadDeath 等。 所以错误是很难处理的,一般与硬件及运行实际环境有关,一般来说程序员是无法处理这些错 误的,我们在编程中,可以不去处理这类错误。 例如内存溢出,需要的内存超出了 java 虚拟机管理的内存范围
//递归模拟内存溢出
public class Test01 {
public static void main(String[] args) {
A(0);
}
}
public static void A(int num){
num += 1;
A(num);
}
Error与Exception的区别:
Error错误,就好像我们开车去上班,结果半路车坏了,那么这种问题我们是没办法提前预知道的
Exception 就好像,我们开车去上班,我们广播中已经提示了A路线堵车,那么我们就可以换条路线即可。
2)、Exception类:
Exception 类所有异常类的父类,其子类对应了各种各样可能出现的异常事件。 Error 是程序无法处理的错误,但是 Exception 是程序本身可以处理的异常,在程序中应当尽可能去处理这些异常。
3)、运行时异常RuntimeException:
RuntimeException 和 他 的 所 有 子 类 异 常,都 属 于 运 行 时 期 异 常 。
例如: NullPointerException 、 ClassCastException 、 IndexOutOfBoundsException 、 ArithmeticException 等。 因为程序编译时异常不能被检查出,所以又称为 不检查异常 ( UnCheckedException ) 。 运行时异常一般是由程序逻辑错误引起的,所以在编写程序时,我们应该从逻辑角度尽可能避 免这类异常的发生。 当出现 RuntimeException 的时候,系统将自动检测并将它们交给缺省的异常处理程序(虚 拟 机 接 管 并 处 理 ) , 用 户 可 以 不 必 对 其 处 理 。
比 如 : 我 们 从 来 没 有 人 去 处 理 过 NullPointerException 异常,它就是运行时异常,并且这种异常还是最常见的异常之一。 ArithmeticException 异常,异常算术条件时抛出。 例如: “ 除以零 ” 的整数会抛出此类的一 个实例。
//空指针异常
public class Test01 {
public static void main(String[] args) {
int [] nums = null;
System.out.println(nums.length);
}
}
4)、编译时异常:
要求程序员在编写程序阶段必须预先对这些异常进行处理,如果不处理编译器报错,因此得名编译时异常
public class BianYiDemo {
public static void main(String[] args) throws ParseException {
String str = "2022-07-09";
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
Date date = format.parse(str);
System.out.println(date);
}
}
3、异常处理
1)、throws抛出异常:
public static void main(String[] args)throws ParseException{
B(); //此处爆出红线
}
public static void B() throws ParseException{
Date parse = new SimpleDateFormat("yyyy-MM-dd").parse("2024-01-05 00:00:00");
System.out.println(parse);
}
throws 关键字表示,抛出异常,跟在方法后,后可跟多个异常,当前虽然可以抛出不处理,抛出异常的方法被调用处,还是要处理!
【注意】
throws虽然抛出异常,当前代码块中的红线消失可以运行,但是如果代码逻辑或者数据错误,还是会爆出异常,这是因为,throws是负责将异常向上抛出,谁调用此方法谁就负责处理这个异常,那么此处的main方法将异常继续向上抛出,那么main方法是JVM调用的,所以就相当于异常抛给了JVM,然而JVM没有处理这个异常,所以异常信息还是会打印出来!
2)、try-catch捕获异常:
public static void main(String[] args) {
try { // try块可能为出现异常的代码段
B();
} catch (ParseException e) { // catch 块为捕获异常(要捕获的异常类型)
//处理异常的方式
System.err.println("您的时间格式错误!请修正!!");
}
}
public static void B() throws ParseException {
Date parse = new SimpleDateFormat("yyyy-MM-dd").parse("2024=01-05 00:00:00");
System.out.println(parse);
}
try块表示发生异常的代码;
catch表示捕获异常,随后打印异常信息,也可以继续向上抛出!
3)、try-catch-finally:
try catch 后还可接finally块,而finally块中代码无论异常是否发生,都会执行!!常用于关闭资源等操作!
public static void main(String[] args) {
try {
C();
} catch (ArithmeticException e) {
System.err.println("分母为0了!!!!快去检查!!!!!");
}finally {
//常常用于关闭关键资源
System.out.println("我是 finally 数据块!,如果出现异常,那就让我关闭数据库资源!");
}
}
//定义一个方法 模拟分母为0的异常情况
public static void C() throws ArithmeticException {
int num1 = 10;
int num2 = 0;
System.out.println("我正常打开了数据库资源!!!!");
System.out.println(num1/num2);
System.out.println("我正常关闭数据库资源!!!!");
}
【思考】
1.如果 异常没有抓住 或者 没有出现异常 的情况下,finally 还会执行嘛? 都会执行
2.如果 遇见return 关键字,那么finally 还会执行嘛? finally 不会收到return影响的!
【finally块不会被执行的情况】有4种特殊情况,finally块不会被执行:
1.finally语句块中发生了异常 (中断了)
2.前面的代码中执行了System.exit()退出程序
3.程序中所在的线程死亡
4.关闭CPU
4)、try-finally:
不去处理异常,但是finally还是会执行
try {
C();
} finally {
//常常用于关闭关键资源
System.out.println("我是 finally 数据块!我关闭数据库资源!");
}
//....
5)、getMessage和e.PrintStackTrace区别:
try {
C();
} catch (ArithmeticException e) {
System.err.println("分母为0了!!!!快去检查!!!!!");
//System.out.println(e.getMessage());
e.printStackTrace();
} finally {
//常常用于关闭关键资源
System.out.println("我是 finally 数据块!我关闭数据库资源!");
}
//.....
getMessage:获取异常简单的描述信息。
语法格式:String msg = exception.getMessage();
PrintStackTrace:打印异常追踪的堆栈信息,比较适合于程序的调试阶段
语法格式:exception.printStackTrace();
6)、throw关键字:
throw关键字为手动抛出异常
通过自己判断出现异常的地方,来手动throw抛出异常,因为是运行时异常,在调用处可以不去处理.
在后面的学习中,我们会慢慢发现,throw通常会提供一种方法的多样性,常常搭配if结构一起使用!
public static void main(String[] args) {
try {
C();
} catch (Exception e) {
System.out.println("分母为0!!!");
}
}
//定义一个方法 模拟分母为0的异常情况
public static void C() {
int num1 = 10;
int num2 = 2;
if(num2==0){
throw new ArithmeticException("我是throw关键字 ---> 分母为0啦!");
} else {
System.out.println(num1/num2);
}
}
7)、throw和throws:
不同点:
-
位置不同。throws用在方法上,后边跟的是异常类,可以跟多个异常类。throw用在方法内,后面跟的是异常对象
-
功能不同。
-
throws用来声明异常,让调用者只知道该功能可能出现的问题,throws表示出现异常的一种可能性,并不一定会发生这些异常
-
throw抛出具体的问题对象。执行throw则一定抛出了某种异常对象
-
相同点:两者都是消极处理异常的方式
4、自定义异常:
1)、定义:继承Throwable或者他的子类Exception的用户自己定义的异常类。前面的内容提到的都是系统有的异常类。
2)、在程序中使用自定义异常的步骤:
-
创建自定义异常类
-
编写异常信息
-
通过throw关键字使用自定义异常类
例如:
class AgeException extends Exception {
public AgeException(String message) {
super(message);
}
}
public class Test03 {
//用户输入年龄方法
public static void input() {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入姓名:");
String name = scanner.next();//接收键盘上姓名的输入
System.out.println("请输入年龄:");
while (scanner.hasNext()) {
//Scanner类中的hasNext()方法可以判断下一个输入是否为整数,整数才会进入到循环体,该程序利用这个特点并且结合if判断语句来控制年龄的输入是否<0
try {
int age = scanner.nextInt();//接收键盘上年龄的输入
if (age < 0) {
throw new AgeException("年龄不能为负数");//2、一旦出现年龄输入为< 0的情况,就会通过throw抛出一个AgeException类的对象并且捕获处理
}
System.out.println("姓名" + name);
System.out.println("年龄" + age);
break;
} catch (AgeException e) {
System.out.println(e.getMessage() + "请重新输入:");
}
}
}
public static void main(String[] args) {
input();
}
}
5、final、finally、finalize的区别:
-
final用于声明属性,方法和类,分别表示属性不可交变,方法不可被重写,类不可继承。
-
finally是异常处理语句结构的一部分,表示总是执行。
-
finalize是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,供垃圾收集时的其他资源回收,例如关闭文件等。
本电子书目录:
《Java基础的重点知识点全集》