Java中的异常
文章目录
- 一、Java中的编译
- 1.1 什么是编译?
- 1.2 Java编译过程的核心步骤
- 二、异常
- 2.1 异常的结构
- 2.1.1 编译时异常
- 2.1.1.1 定义和特点
- 2.1.1.2 常见的一些编译时异常
- 2.1.2 运行时异常
- 2.1.2.1 定义和特点
- 2.1.2.2 常见的一些运行时异常
- 2.2 处理异常
- 2.2.1 throw
- 2.1.1.1 异常的参数
- 2.1.1.2 throw抛出异常的特点
- 2.2.2 throws
- 2.2.3 try catch
- 2.2.3.1 catch未捕捉到异常则之后的代码不再执行
- 2.2.3.1 try抛出异常之后的代码不再执行
- 2.2.3.2 catch可以捕获多个异常
- 2.3.3.3 如果捕获的异常是父子关系,则子在前父在后
- 2.3.4 finally
一、Java中的编译
代码编译期间是
Java程序生命周期的第一阶段
,负责将人类可读的源代码转换为JVM可执行的字节码。核心目标是检查代码的规范性并生成与平台无关的中间文件。
1.1 什么是编译?
在 Java 中,编译是指将开发者编写的 Java 源代码(.java 文件) 转换为 字节码(.bytecode,即 .class 文件) 的过程。这一过程由 Java 编译器(javac) 完成,是 Java 程序运行前的关键步骤。
1.2 Java编译过程的核心步骤
-
编写源代码
:
开发者编写 .java 文件,其中包含符合 Java 语法规则的代码。也就是创建了一个java源代码(.java文件)
-
调用编译器(javac)
:
使用命令行工具 javac 在cmd中编译或集成开发环境(IDE)触发编译。
例如:在cmd中配置好环境后,使用javac命令生成下面字节码文件。
javac Test.java
编译为字节码
:
编译器逐行检查语法错误(如拼写错误、类型不匹配等),若无错误,生成与源代码对应的 .class 文件。字节码是一种中间代码,独立于具体操作系统,但需要由 Java 虚拟机(JVM) 执行
。也就是说一次运行到处跑,生成的字节码文件在任何电脑都可以跑,但是需要安装Java软件,Java软件中存在Java虚拟机,通过Java虚拟机(JVM)解释字节码文件运行程序。
以下为目录下生成.class文件
二、异常
在编程过程中,异常是程序执行过程中发生的非预期事件,它会打断程序的正常运行。
例如:
空指针异常:NullPointerException
public class Test2 {public static void main(String[] args) {int[] arr = null;System.out.println(arr.length);}
}
简单来说,可以把异常想象为生活中的意外事件。程序就是我们规划好的流程,而异常就是流程中可能出现的生活意外情况,例如:想要出去玩,发现车没油了,亦或者路上堵车了等等突发时间。处理异常就像我们提前准备应对突发事件的方案。
2.1 异常的结构
所有的异常都继承于一个父类Throwable。而Throwable父类下又分为两个子类:Error(错误)和Exception(异常)。异常又可以分为:编译时异常(受查异常)和运行时异常(非受查异常)。
2.1.1 编译时异常
在java中,编译时异常是程序在
编译阶段必须处理
的异常类型,由编译器强制检查并由程序员显式处理
,否则无法通过编译。
2.1.1.1 定义和特点
- 定义:编译时异常是Java编译器在代码编译期间检测到的潜在问题,通常与外部资源或环境相关(如文件操作、数据库连接等),需要开发者提前处理。
- 特点:
-
- 强制处理:必须通过try-catch捕获或在方法声明中用throws声明,否则的话就会编译错误。
-
- 可预测性:程序中可以预测出现的一些问题(文件不存在、网络中断等),可以直接在代码编写阶段防范和处理。
通俗的来讲,编译时异常就是生活中可以预见的一些异常:
假如你明天要去逛街,提前做好了以下准备
- 查看天气预报–>发现可能下雨—>带伞。
- 出行是否方便–>发现明天星期天可能路上拥挤–>不骑小电驴,改成走路了。
2.1.1.2 常见的一些编译时异常
IOException(输入输出异常)
文件读写、网络通信、流操作失败可能会导致输入输入输出异常
子类:
- FileNotFoundException:文件路径不存在。
- EOFException:读取文件时意外到达末尾。
代码例子:
public static void main(String[] args) {FileReader fileReader = new FileReader("z20250326");}
出错:
原因:
这是因为使用FileReader方法未进行处理异常,跳转到这个方法中可以发现这个方法在定义的时候声明了FileNotFoundException异常。它告诉调用者这个方法可能会抛出这个异常,调用者需要处理这些异常。
也就是说这个方法在定义的时候,声明了你使用这个方法可能会出现FileNotFoundException异常(文件不存在)。它在定义这个方法的时候,并没有处理这个方法中可能会出现的问题异常,所以当你使用它这个方法的时候,你必须要处理这个异常,不然程序就不让你编译(也就是Java在这个类当中假如读取不到文件,它会throw扔出一个异常,如果读取到了就读取,那么外面使用这个类的人不知道会出现这个问题,于是在类的上面使用throws声明这个异常,告诉使用它的人,注意了使用我可能会出现这个问题,你在使用前要注意处理一下)。就拿上面的例子进行说明,你和好朋友要去逛街,但是好朋友和你说了明天要下雨,你必须带一把伞,但是你并没有带,那么也就逛不了街。
处理:
在使用的时候也抛出异常让JVM处理或者使用try-catch自己处理。
SQLException(数据库操作异常)
数据库连接失败、SQL 语法错误、事务回滚会导致数据库操作异常。
常见的原因一般有:无效的 SQL 语句、数据库连接超时、权限不足。
ClassNotFoundException(类未找到异常)
场景:动态加载类时,类路径中不存在目标类。
常见于:反射(Class.forName())、依赖缺失、JAR 包未正确导入。
2.1.2 运行时异常
运行时异常是程序在运行期间由代码逻辑错误或者意外操作引发的异常,属于RuntimeException以及其子类,是非受检异常,编译器不强制要求处理。
2.1.2.1 定义和特点
定义
运行时异常通常由代码逻辑错误或者资源使用不当引起,例如空指针、数组越界等。这些异常在编译阶段不会被检查,而是在程序运行时触发。
特点
- 无需显式处理:编译器不强制要求使用try-catch,或throws,但未处理时,程序会终止并抛出错误信息。
- 不强制处理,多数运行时异常可以通过代码逻辑优化避免,如判断是否为空,索引检查等
// 避免空指针:提前检查对象是否为null
if (user != null) {System.out.println(user.getName());
}
- 运行时异常均继承RuntimeException。
整体来说,运行时异常属于突发意外,就比如开车出去玩,车轮爆胎,刹车不灵,误把刹车当成油门踩等操作。
2.1.2.2 常见的一些运行时异常
空指针异常(NullPointerException)
public static void main(String[] args) {int[] arr = null;System.out.println(arr.length); //空指针异常}
这里空指针异常不会编写代码时候显示错误,而是在运行程序之后才会出现的异常叫做空指针异常。
而空指针异常也是可以通过代码逻辑上的优化是可以避免的。
public static void main(String[] args) {int[] arr = null;if(arr != null) {System.out.println(arr.length);}}
算数操作异常(ArithmeticException)
public static void main(String[] args) {System.out.println(10 / 0); //算数异常}
Java语法中,除数不能为0,否则会出现算数异常。
数组越界异常(ArrayIndexOutOfBoundsException)
public static void main(String[] args) {int[] arr = new int[5];System.out.println(arr[5]); //越界异常}
错误的类型强制转换异常(ClassCastException)
class Animal {}
class Dog extends Animal{}
class Duck extends Animal {}public static void main(String[] args) {Animal animal = new Dog();Duck duck = (Duck) animal;}
这里dog类对象发生向上转型,此时animal可以发生向下转型也就是狗,但是不能向下转型变成鸭子。也就是说可以把所有狗说成动物,但是所有的动物不能是鸭子。
2.2 处理异常
处理异常主要包含五个关键字:throw、throws、try、catch、finally。
2.2.1 throw
throw在Java语法中是手动抛出异常的关键字,可以抛出异常的对象,并且可以将错误信息给使用者。语法形式为:throw new xxxException(“异常产⽣的原因”)。
如下方代码
public static int div(int x,int y) {if(y == 0) {throw new ArithmeticException();}return x / y;}public static void main(String[] args) {int ret = div(20,10);System.out.println(ret);int ret1 = div(20,0);System.out.println(ret1);}
解析
在这里自定义方法div,在Java中语法规定div除数不能为0,所以小编这里除数也不能为0,如果为0,手动抛出算数异常,因为异常是一个类,所以要new一个对象出来,如果不为0则正常返回两个数相除的值。因此在main函数中调用div函数,使用两个测试用例进行测试,第一次使用不为0,正常得到两个数相除;第二次除数为0,则会抛出算数异常。
2.1.1.1 异常的参数
按照上面例子跳转到ArithmeticException中:
可以发现ArithmeticException是一个类的构造方法,除了该构造方法外还重载另外一种构造方法,它的参数是字符串,调用的是父类运行时异常的方法。这里要跳转的太多了,小编这里直接实践出真知。
public static int div(int x,int y) {if(y == 0) {throw new ArithmeticException("算数异常,y不能为0");}return x / y;}public static void main(String[] args) {int ret1 = div(20,0);System.out.println(ret1);}
这里可以通过传递给异常内的参数传递字符串,打印出错的信息。
2.1.1.2 throw抛出异常的特点
1. 抛出异常后,后续的代码不再运行
在这里抛出异常后的代码直接为错误。又或者是下面这种情况:抛出异常后异常后面的代码全部都不执行。
2.throw必须写在方法体的内部
因为throw抛出异常后后面代码就都不会再执行,所以如果需要判断异常,需放在方法体的内部,否则就直接抛出异常后面的代码也不会执行,导致得不到想要的结果。
3. 抛出的对象可以是Error也可以是Expection
上面已经展示过了Expection的异常抛出,下面展示throw也是可以抛出Error。
4. 如果throw抛出的异常是编译时异常,必须得处理
上面代码中抛出了一个IOExpection,输入输出异常属于编译时异常,在扔出的时候,需要捕获try-catch或者使用throws声明抛出。
2.2.2 throws
throws也是扔的意思,但是再Java中它代表着声明异常。那么throws也就不是处理了这个异常,它只是在告诉使用这个方法的人,使用该方法需要注意可能会抛出以下异常,你需要处理一下。
-
throws是在方法的参数列表后声明异常
-
相比throw,throws更重要的是代表如果扔出的异常为编译时异常的时候,必须要处理或者使用throws声明继续抛出给使用它的人去处理。而如果是运行时异常,可以使用throws声明异常也可以不声明异常。
3. 如果抛出多个异常采用逗号的形式隔开
2.2.3 try catch
throws并不是真的处理了异常,它和throw搭配,一个是扔出异常一个是声明异常,并不会处理异常,它是把异常的处理传递给了调用该方法的代码。而处理异常则是需要try-catch来捕获处理。
在Java中,处理异常的关键字为try-catch,语法形式为:
try {//可能出现的代码错误代码
}catch(xxxExpection e) {//处理异常的代码//或者打印错误的信息:e.printStackTrace(); //用于打印完整的错误信息
}
代码讲解
public static int div(int x,int y) {if(y == 0) {throw new ArithmeticException("算数异常,y不能为0");}System.out.println("抛出异常后的代码");return x / y;}public static void main(String[] args) {try {div(10,0);}catch (ArithmeticException e) {System.out.println("这里处理了异常");}}
运行结果:
在这里处理了异常就不会再次出现异常提醒,而是处理了异常之后的代码。try的代码块中填写的是可能出现异常的代码,catch则是捕获异常的类型,如果捕获到该异常,则执行代码块的内容,也就是处理异常。
2.2.3.1 catch未捕捉到异常则之后的代码不再执行
可以捕捉到异常的代码运行结果:
这里是没捕捉到异常之后的代码
在这里捕捉到了异常,则后续代码会继续执行,而div方法出现的是算数异常而不是空指针异常,此时只能捕获到空指针异常,于是这个异常又没有处理,向上继续扔出,直到扔到JVM中,JVM识别到这里出现了异常,于是中断程序执行,后续代码也不再执行了。
2.2.3.1 try抛出异常之后的代码不再执行
在这里运行上方程序之后,此时调用div时候已经抛出异常了,那么抛出异常后的代码不再执行,直接去捕获异常了。
2.2.3.2 catch可以捕获多个异常
catch可以捕获多个异常,但是只会执行捕获到正确的异常的那个代码块的代码。如果一个方法抛出多个异常那也可以同时处理,但是小编不推荐这样处理,因为这样分不清方法是哪里出现了异常是空指针异常呢还是算数异常呢?这个又需要进行调试反而极不方便。这里下面简单介绍一下多处理方式:
2.3.3.3 如果捕获的异常是父子关系,则子在前父在后
父在前的情况:则直接会报错,编译都通过不了
所以捕获的异常存在父子类关系,必须要父类在前,子类在后。如以下代码:
2.3.4 finally
finally故名思意最终地,再Java中finally是最后执行的,而且是一定会执行的,它用于关闭一些使用的工具,资源等。
代码讲解
在之前讲述了捕获到了异常之后则不在捕获,但是对于后面有finally的情况下,无论如何都会执行,就如同上面代码块,而且是执行了catch捕获后就会继续执行finally代码块的内容。综合:因为finally总是会执行,所以可以在finally中关闭一些上面调用过的资源,就算捕获到了异常也会执行finally代码块的内容,如下图代码片段: