目录
1. 什么是异常?
异常其实就是代表程序出现的问题。
我们都知道,在编写代码时,我们时常会编写出一些 bug,那么这些 bug,它们有一些可能是异常,有一些可能不是异常。
这里有一个误区需要各位注意,学习异常不是为了让我们以后在写代码的过程中不出现异常,但其实也很难避免不出现异常,学习异常是为了让我们知道在程序出现异常之后,该怎么去处理它。
2. 异常家族体系介绍
我们知道,在Java中,有各种各样的异常,它们其实是一个大的家族,而这个家族的首领就是 java.lung.Throwable。
而 Throwable 又分为俩大家族,一个是Error(错误),一个是Exception(异常)。
如下图中关系所示
2.1 Error
(1)Error 是一个类,在java.lang 包下,它继承了 Throwable 类,是 Throwable 类的子类;
(2)Error 通常代表系统级别错误,属于严重问题;
(3)系统一旦出现问题,sun公司会把这些错误封装成 Error 对象;
(4)Error 是 sun 公司自己用的,不是给我们程序员用的,因此对于我们开发人员来讲,不需要特别关注。
2.2 Exception
2.2.1 Exception 详情
(1)Exception 是一个异常类,也在 java.lang 包下,它和 Error 一样都继承了 Throwable 类,是 Throwable 类的子类;
(2)Exception 叫做异常,代表程序中可能出现的问题,是异常体系中的最顶层父类,所有的异常类都继承了 Exception 异常类;
(3)我们通常会用 Exception 以及它的子类来封装程序出现的问题。异常又可以分为运行时异常与编译时异常。
2.2.2 运行时异常
RuntimeException 及其子类,编译阶段不会出现异常提醒。是在程序运行的时侯才会出现的异常,一般是由于参数传递出现的异常(如:数组索引越界异常)。
2.2.3 编译时异常
编译时异常没有继承 RuntimeException ,直接继承了 Exception ,在我们编写代码时就会出现的异常,在编写代码时必须手动处理解决,作用在于提醒程序员,通常情况下IDEA编辑器会给出我们相应的的异常处理提示,我们可以采取对应的方案去解决(如:日期解析异常)。
2.2.4 Exception 分类总结
对于我们程序员来说,需要着重掌握 Exception 这一家族体系中的各成员。
3. 从类加载的全过程深入理解编译时异常与运行时异常
3.1 类加载的全过程
我们都知道,一个Java类,从编写到运行是需要经历一个过程的,如下图所示,我们编写一个 Java 代码文件,想要运行它并得到结果是需要以下的一个过程的。
首先(1)我们编写Java文件;
其次(2)编译阶段,编写完成之后,执行 Javac 命令,将编写的 Java 文件编译成字节码文件;
再次(3)运行阶段,运行我们编译完成得到的字节码文件;
最终(4)程序运行完毕,输出结果。
3.2 什么是编译时异常?
通过我对上面类加载的过程叙述,想必大家此时对编译时一场应该有一个模糊的了解到了吧,我们知道,在编写代码时,我们编写的其实是 Java 文件,在编译完成之后,我们会通过javac命令使 Java 文件编译成字节码文件,如下图所示,在编译阶段,我们的系统会检查你所编写的代码有没有语法层面的错误,但是不会检查你的代码有没有逻辑层面的错误这样说各位应该明白了吧,因为此时我们写的代码还没有运行呢,就所有逻辑错误,系统在没有运行之前也是件查不到的!!!
此外,我们的 javac命令在编写为字节码文件时,不仅会做语法检查,还会对我们的代码做一个优化处理。
例如我在写代码时定义一个字符串 String str = "a" + "b" + "c";javac 在编写为字节码文件的时候,会自动将 str 做优化,实际编写为字节码文件之后是 String str = "abc";
还有就是泛型,我们所写的泛型其实在 Java 中属于伪泛型,它只有在编写代码时我们可以看得到,在javac编译成字节码文件之后,泛型就消失不见了,我们想一想,你在给一个List容器添加了泛型之后,假设你添加的泛型为<Integer>,若你再添加 String 类型的数据,各位想想,是不是会报错,这种通过泛型约束添加数据类型也属于编译时异常,需要在代码编写阶段就提前处理好的。
3.3 什么是运行时异常?
刚才我们详细的了解了什么是编译时异常,也那么现在我们再来看运行时异常,可以说是非常简单了吧!
编译时异常,是对我们代码语法层面的检查,运行时异常,就是我们程序运行之后才出现的逻辑异常,执行编译完成得到的字节码文件,再运行得出结果,如果在编译时检查除了一部分语法异常,那么逻辑异常就会在运行阶段给我们展示出来。
3.4 Java问什么要分为编译时异常与运行时异常
不知道各位有没有想过,Java问什么要分为编译时异常与运行时异常?直接把他们归为一类不好吗?
其实通过上方的讲解,各位应该也知道一个大概了,因为在编译阶段,我们的代码还尚未运行,即便是有逻辑错误,但程序在运行之前也无法检查出来,这个阶段,其实更多的是在提醒我们程序员检查本地语法是否有误。而且,在编译阶段,不仅是在检查语法是否有误,同时也做了代码优化这一步骤,将一些错误和对程序性能的优化提前到了程序运行之前,提高了程序的执行效率,同时也减少了出错率。
而运行时异常只有程序运行之后我们才能知道,它的真正原因就是因为代码出错而导致的异常,与语法无关;如果发生这种异常,我们就可以根据它的提示归根究源,迅速找到问题所在,从而解决程序出现的问题,在某方面也提高了程序的健壮性。
下面我简单给各位列举几个异常,各位就会对异常有更加深入的理解了。
4. 编译时异常代码展示
4.1 日期格式转换异常
我编写了一段代码,做了一个字符串日期格式转换的操作
public class TestException {
public static void main(String[] args) {
// 演示编译时异常
// 定义一个日期字符串
String time = "2023年8月3日";
// 创建一个 SimpleFormatter 的对象
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日");
// 传入 time 字符串转化
Data data = sdf.parse(time);
// 输出 data
System.out.println(data);
}
}
但在IDEA编辑器中,它提醒我在进行 parse 操作时,可能会出现异常,我们按键盘上的 alt + Enter 键,IDEA就可以给出我们相应的解决方案,这种异常就属于编译时异常,是可以在编写代码时解决的异常
4.2 IO流文件找不到异常
定义一个 FileInputStream 的对象,但这里它却报了一个异常,我们鼠标点过去 Alt + Enter,它会让我们对异常做处理。
那么这里会有什么异常发生呢?我们的代码明明写的没有错为啥什么会出现异常呢?
这种异常就属于是编译时异常,一般情况下我们本身编写没有问题,而是语法检查时可能会出现的问题,像上面的IO流,我们本身创建对象没有问题,但是程序不知道你填写的路径是否有这个文件的存在,因此才会爆出异常。
5. 运行时异常代码展示
5.1 除0异常
如下代码,我们定义一个变量a = 10,一个变量b = 0,让变量 c = a/b,再打印出c的值。
其实我们可以看出,每一行代码在语法层面都没有问题,但是0不能作为除数着我们是知道的,因此在编译阶段,这种错误不会被提示出来,只有在代码运行之后陈程序才知道有这样一段逻辑运算,这种异常就属于运行时异常。
5.2 数组越界异常
如下所示,我定义一个 int 数组,长度为3,但我却给它赋四个值,它就会在运行的时候爆出数组越界异常(ArrayIndexOutOfBoundsException)。
可以看到,我们的每一行代码在语法层面上其实都没有错误吧,只是一个简单的赋值操作,但是,我们的程序在运行之前根本就不知道数组没有第四个位置,只有才运行之后,数组出现在内存时它才知道,也就是说必须在运行时才能检验出来的异常就是运行时异常。
6. 补充
本篇文章主要说明 Java 中的异常家族体系,说明一些理论和概念,重点就是要掌握的编译时异常和运行时异常两种异常;
此外,下篇我会重点讲解这些异常该如何处理,异常处理时需要注意的细节,属于实战环节,包括面试时会问到那些关于异常的问题,因为内容过多,因此拆分成上下两篇。
下篇文章链接如下,感兴趣的同学可以接着学习下篇内容