文章目录
Java 的异常处理
在程序设计和运行的过程中,发生错误是不可避免的。为此,Java 提供了异常处理机制来帮助程序员检查可能出现的错误,保证程序的可读性和可维护性。Java 中将异常封装到一个类中,出现错误就会抛出异常。
Java 语言是一门面向对象的编程语言,因此,异常在 Java 语言中也是作为类的实例的形式出现的。当某一方法中发生错误时,这个方法会创建一个对象,并且把它传递给正在运行的系统。这个对象就是异常对象。
通过异常处理机制,可以将非正常情况下的处理代码与程序的主逻辑分离,即在编写代码主流程的同时在其它地方处理异常。
生活和程序中的异常
生活中常见的异常
有些人平时会开车上班,从家到公司这段路程需要30分钟,于是就提前40分钟出门了。可是上班的途中遇到了堵车,原本30分钟的路程我们用了两个小时。最后,结局很美好,我们迟到了。我们原本的计划是30分钟到达公司,结果在路上突发了一些情况导致我们没有能够完成我们的任务,这就是生活中的一个异常场景。
程序中常见的异常
在程序中,错误可能产生于程序员各种没有预料到的情况,或者是超出了程序员的可控范围的环境因素。例如用户的坏数据、用户试图打开一个根本不存在的文件等等。异常是一个在程序执行期间发生的事件,它中断了正在执行的程序的正常指令流。而在 Java 中这种在程序运行时可能出现的一些错误,我们都管它叫做异常。
异常的分类
在 Java 类库中每个包中都定义了异常类,而这些所有的类都是 Throwable 类的子类
Throwable 类是所有异常类的超类
Throwable 类派生出了两个子类,分别是 Exception 和 Error,其中 Error 类及其子类它是用来描述 Java运行系统中的内部错误以及资源耗尽的错误,这一类错误的比较严重的,而且我们是无法解决的。而 Exception 类被称为非致命性错误,是可以通过捕捉处理使程序继续执行的一种错误。
Exception 类又可以根据错误发生的原因分为运行时异常和非运行时异常,所谓的运行时异常它的名字叫做 RuntimeException。
Exception 异常
Exception 异常是可以通过处理去解决的异常:
比如汽车没有油了,我们可以到加油站去加油;轮胎爆胎了,我们可以把备胎换上;像这样一些问题我们是可以自己解决的
所谓的 Exception 异常就是会影响程序正常运行,但可以被解决的问题
RuntimeException
1、RuntimeException是那些可能在 Java 虚拟机正常运行期间抛出的异常的超类,即程序运行过程中产生的异常
可能在执行方法期间抛出但未被捕获的 RuntimeException 的任何子类都无需在 throws 的字句中进行声明
2、比如空指针异常、数组下标越界、强制转换类异常、算术异常等等,这些异常一般是由程序逻辑错误引起的
//空指针异常
package com.zyt.lesson01;
public class Demo {
public static void main(String[] args) {
Object obj = null;
System.out.println(obj.hashCode());
}
}
/*
Exception in thread "main" java.lang.NullPointerException
at com.zyt.lesson01.Demo.main(Demo.java:6)
*/
//数组下标越界异常
package com.zyt.lesson01;
public class Demo {
public static void main(String[] args) {
int arr[] = new int[6];
System.out.println(arr[10]);
}
}
/*
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 10
at com.zyt.lesson01.Demo.main(Demo.java:6)
*/
//算术异常
package com.zyt.lesson01;
public class Demo {
public static void main(String[] args) {
int a = 1/0; //除数不能为0
}
}
/*
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.zyt.lesson01.Demo.main(Demo.java:5)
*/
Error 错误
Error 是指系统发生了重大的错误,是不应该试图捕获的严重问题:
例如:字节码文件无法解析、Java 虚拟机的资源耗尽、配置文件格式不规范
我们在代码中是无法通过修改代码去解决 Error 级别的错误的,可以通过重新编写程序、重新分配系统资源去解决
package com.zyt.lesson01;
public class Demo {
public static void main(String[] args) {
int a = 1/1
System.out.println(a);
}
}
//Error:(5, 20) java: 需要';'
如何捕捉处理异常
在 Java 中,如果某个方法抛出异常,既可以在当前方法中进行捕捉,然后处理该异常,也可以使用 throws 关键字将异常向上抛出,由方法调用者来处理。
main 方法向上抛出的异常是由 java 虚拟机去处理的,我们干涉不了,所以在 main 方法中我们尽量用 try catch 去捕获异常,而不要用 throws 去向上抛出异常。
1、自动捕捉异常
Java 虚拟机运行期间,如果程序发生了异常,Java 虚拟机会自动地捕捉异常
但 Java 虚拟机只能捕获这个异常,却无法去处理这个异常
2、try catch 语句捕捉并处理异常
我们可以通过 try catch 语句对发生的异常进行处理
语法格式:
try{
被捕获的代码
}catch(){
对异常的处理
}
Execption 是 try 代码块传递给 catch 代码块的变量类型,e 是变量名。catch 代码块中的语句"e.getMessage();"用于输出错误性质。通常,异常处理常用以下3个函数来获取异常的有关信息:
1、getMessage()函数:输出错误性质——用于 Error
2、toString()函数:给出异常的类型与性质
3、printStackTrace()函数:指出异常的类型、性质、栈层次以及出现在程序中的位置
package com.zyt.lesson01;
public class Demo {
public static void main(String[] args) {
try {
int a = 1/0;
} catch (ArithmeticException e) {
System.out.println("发生了算术异常,请管理员及时处理!");
}
}
}
3、多重 try catch 代码块
由上至下逐级捕获异常,当正好是 Exceotion2 的异常我们就执行 catch(some Exceotion2 e) 后大括号内的处理代码,处理完之后,我们就直接结束 try catch 语句,不再向下执行
try{
语句1;
语句2;
}catch(some Exceotion1 e){
......
}catch(some Exceotion2 e){
......
}catch(some Exceotion3 e){
......
}
package com.zyt.lesson01;
public class Demo {
public static void main(String[] args) {
try {
Object obj = null;
obj.hashCode();
}catch(NullPointerException e){
System.out.println("发生了空指针异常!");
}catch (ArithmeticException e) {
System.out.println("发生了算术异常,请管理员及时处理!");
}catch(ClassCastException e){
System.out.println("发生了类转换异常!");
}
}
}
//输出结果: 发生了空指针异常!
4、异常的中断机制
当出现异常之后,异常之后的代码就会被屏蔽掉,不会再执行了,这就是异常的中断机制
异常产生后,如果不做任何处理,程序就会被终止。例如,将一个字符串转换为整型,可以通过 Integer 类的 parseInt( ) 方法来实现。但如果该字符串不是数字形式,parseInt( ) 方法就会抛出异常,程序将会在出现异常的位置终止,不再执行之后的语句。
package com.zyt.lesson01;
public class Demo {
public static void main(String[] args) {
try{
System.out.println("第1行");
System.out.println("第2行");
System.out.println("第3行");
}catch(Exception e){
e.printStackTrace();
}
}
}
/*
输出结果:
第1行
第2行
第3行
Very Good
*/
当 try 语句块中的语句发生异常时,程序就会调转到 catch 代码块中执行,执行完 catch 代码块中的程序代码后,将继续执行 catch 代码块后的其它代码,而不会执行 try 语句块中发生异常语句后面的代码。
由此可知,Java 的异常处理是结构化的,不会因为一个异常影响到整个程序的执行。
package com.zyt.lesson01;
public class Demo {
public static void main(String[] args) {
try{
System.out.println("第1行");
int num = 1/0;
System.out.println("第2行");
System.out.println("第3行");
}catch(Exception e){
e.printStackTrace();
}
System.out.println("Very Good");
}
}
/*
输出结果:
第1行
Very Good
java.lang.ArithmeticException: / by zero
at com.zyt.lesson01.Demo.main(Demo.java:7)
*/
思考:如果在循环中出现了异常,会不会把循环中断?
package com.zyt.lesson01;
public class Demo {
public static void main(String[] args) {
try {
for(int i=0;i<5;i++){
System.out.println("输出"+i+"行");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/*
输出结果:
输出0行
输出1行
输出2行
输出3行
输出4行
*/
package com.zyt.lesson01;
public class Demo {
public static void main(String[] args) {
try {
for(int i=0;i<5;i++){
System.out.println("输出"+i+"行");
int a = 1/i;
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/*
输出结果:
输出0行
java.lang.ArithmeticException: / by zero
at com.zyt.lesson01.Demo.main(Demo.java:8)
*/
结论:在循环中出现了异常,会把循环中断
有什么方法能让循环中即使发生了异常,循环仍不终止?
如下:
package com.zyt.lesson01;
public class Demo {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
try {
System.out.println("输出" + i + "行");
int a = 1 / i;
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
/*
输出结果:
输出0行
java.lang.ArithmeticException: / by zero
输出1行
输出2行
输出3行
输出4行
at com.zyt.lesson01.Demo.main(Demo.java:8)
*/
恢复机制—finally
finally 语句块就是为了防止像现实生活中的电脑突然断电关机,快结束的工作因为未保存而功亏一篑的情况而设计的
finally 代码块
Java 语言的异常捕获结构由 try、catch、finally 三部分组成。其中,try 语句块存放的是可能发生异常的 Java 语句;catch 程序块在 try 语句块之后,用来激发被捕获的异常,对异常进行处理;finally 语句块是异常处理结构的最后执行部分,无论 try 语句块中的代码如何退出,都将执行 finally 语句块中的语句。
finally 具有一个收尾的功能
异常处理器的语法格式:
try{
被捕获的代码
}catch(异常类型 e){
对异常e的处理
}finally{
最后一定会执行的代码
}
通过异常处理器的语法可知,异常处理器大致分为 try catch 语句块和 finally 语句块
package com.zyt.lesson01;
public class Demo {
public static void main(String[] args) {
try {
System.out.println("打开连接池");
System.out.println("通过连接池读取数据");
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("关闭连接池");
}
}
}
/*
输出结果:
打开连接池
通过连接池读取数据
关闭连接池
*/
package com.zyt.lesson01;
public class Demo {
public static void main(String[] args) {
try {
System.out.println("打开连接池");
int a = 1/0;
System.out.println("通过连接池读取数据");
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("关闭连接池");
}
}
}
/*
输出结果:
打开连接池
java.lang.ArithmeticException: / by zero
at com.zyt.lesson01.Demo.main(Demo.java:7)
关闭连接池
*/
finally 的四种特殊情况
finally 代码块一般情况下都会得到执行,它相当于一个万能的保险,但有四种特殊情况 finally 将不会执行
1、在 finally 代码块中发生了异常
package com.zyt.lesson01;
public class Demo {
public static void main(String[] args) {
try {
System.out.println("打开连接池");
int a = 1/0;
System.out.println("通过连接池读取数据");
} catch (Exception e) {
e.printStackTrace();
} finally {
int b = 1/0;
System.out.println("关闭连接池");
}
}
}
/*
输出结果:
打开连接池
java.lang.ArithmeticException: / by zero
at com.zyt.lesson01.Demo.main(Demo.java:7)
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.zyt.lesson01.Demo.main(Demo.java:12)
*/
2、在前面的代码块中用了 System.exit( ) ,System.exit( ) 的作用是强制中断当前的程序
红点没有了相当于程序停止了
package com.zyt.lesson01;
public class Demo {
public static void main(String[] args) {
try {
System.out.println("打开连接池");
System.exit(0); //强制停止当前的程序
System.out.println("通过连接池读取数据");
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("关闭连接池");
}
}
}
/*
输出结果:
打开连接池
*/
3、程序所在的线程死亡了
package com.zyt.lesson01;
public class Demo {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
try {
System.out.println("打开连接池");
sc.nextLine(); //一直不输入值,手动让线程死亡
System.out.println("通过连接池读取数据");
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("关闭连接池");
}
}
}
/*
输出结果:
打开连接池
此时红灯一直亮着,代表程序一直没有结束
*/
4、关闭 CPU
程序运行时突然停电,电脑关机
Java 中的常见异常
种类 | 说明 |
---|---|
ClassCastException | 类型转换异常 |
ClassNotFoundException | 未找到相应类异常(经常出现在反射中) |
AirthmeticException | 算术异常 |
ArrayIndexOutOfBoundsException | 数组下标越界异常 |
SQLException | 操作数据库异常 |
IOException | 输入输出异常 |
FileNotFoundException | 文件未找到异常 |
IllegalAccessException | 不允许访问某类异常 |
NullPointerException | 空指针异常 |
拓展:
1、变量一般定义之后是一定要赋值的,但静态变量可以不赋值,如果不赋值默认为 null
2、静态变量不能在静态方法中定义
//算术异常
package com.zyt.lesson01;
public class Demo {
public static void main(String[] args) {
int a = 10/0;
}
}
————————————————————————————————————————————————————————————————————————————————————————————————————————————
//未找到相应类异常
package com.zyt.lesson01;
public class Demo {
public static void main(String[] args) throws ClassNotFoundException {
Class c = Class.forName("java.lang.String");
System.out.println(c);
}
}
//输出:Class.java.lang.String
package com.zyt.lesson01;
public class Demo {
public static void main(String[] args) throws ClassNotFoundException {
Class c = Class.forName("java.lang.zyt");
System.out.println(c);
}
}
//抛出 ClassNotFoundException 异常
————————————————————————————————————————————————————————————————————————————————————————————————————————————
//下标越界异常
package com.zyt.lesson01;
public class Demo {
public static void main(String[] args) throws ClassNotFoundException {
int a[] = new int[5];
a[10] = -1;
}
}
————————————————————————————————————————————————————————————————————————————————————————————————————————————
//空指针异常
package com.zyt.lesson01;
public class Demo {
public static void main(String[] args) throws ClassNotFoundException {
Object obj = null;
obj.getClass();
}
}
package com.zyt.lesson01;
public class Demo {
//变量一般定义之后是一定要赋值的,但静态变量可以不赋值,如果不赋值默认为 null
static Object obj;
public static void main(String[] args) throws ClassNotFoundException {
//静态变量不能在静态方法中定义
obj.getClass();
}
}
自定义异常
使用 Java 内置的异常类可以描述在编程时出现的大部分异常情况,除此之外,用户只需继承 Exception 类即可自定义异常类
创建自定义异常的定义:自己创建一个 API 中不存在的异常
//语法格式:
class 自定义异常类 extends 已有的异常类{
}
在程序中使用自定义异常类,大体可以分为以下几个步骤:
1、创建自定义异常类
2、在方法中通过 throw 关键字抛出异常对象
3、如果在当前抛出异常的方法中处理异常,可以使用 try catch 语句块捕获并处理,否则在方法的声明处通过 throws 关键字指明要抛出给方法调用者的异常,继续进行下一步操作
4、在出现异常方法的调用者中捕获并处理异常
有四名运动员参加长跑比赛,一号选手为飞人博尔特、二号选手为中国飞人刘翔、三号选手为美国超级英雄闪电侠、四号选手为一个大猩猩,此时我们自定义一个异常为 NonHumansException,当选手不是一个人的时候就会抛出这个异常。
//自定义异常 NonHumansException
package com.zyt.lesson01;
public class NonHumansException extends Exception{
public NonHumansException(String message){
super(message);
}
}
//测试异常
package com.zyt.lesson01;
public class Demo {
public static void main(String[] args) {
String playerType = "monkey";
try {
if(!playerType.equals("human")){
throw new NonHumansException("有非人类的选手:"+playerType);
}
System.out.println("开始比赛");
} catch (NonHumansException e) {
e.printStackTrace();
}
}
}
/*
输出:
com.zyt.lesson01.NonHumansException: 有非人类的选手:monkey
at com.zyt.lesson01.Demo.main(Demo.java:8)
*/
package com.zyt.lesson01;
public class Demo {
public static void main(String[] args) {
String playerType = "human";
try {
if(!playerType.equals("human")){
throw new NonHumansException("有非人类的选手:"+playerType);
}
System.out.println("开始比赛");
} catch (NonHumansException e) {
e.printStackTrace();
}
}
}
//输出:开始比赛
//优化
public class Demo {
public static void main(String[] args) {
String playerType = "monkey";
try {
if(!playerType.equals("human")){
throw new NonHumansException("有非人类的选手:"+playerType);
}
System.out.println("开始比赛");
} catch (NonHumansException e) {
e.printStackTrace();
}
}
}
package com.zyt.lesson01;
import javax.swing.*;
public class NonHumansException extends Exception{
String message;
public NonHumansException(String message){
super(message);
this.message = message;
}
@Override
public void printStackTrace() {
super.printStackTrace();
JOptionPane.showMessageDialog(null,message,"发生异常",JOptionPane.ERROR_MESSAGE);
}
}
//此时会弹出一个对话框,上面写着,有非人类的选手:monkey
在方法中抛出异常
有时候我们碰到的问题是我们自己解决不了的,比如我们开车的时候没有油了,这个问题我们自己是解决不了的,我们唯一能做的就是把车开去加油站加油。
同样,Java 中我们遇到的一些问题,我们自己是处理不了的,我们需要把这个问题交给我们的上一级来处理。
若某个方法可能会发生异常,但不想在当前方法中处理这个异常,则可以使用 throw、throws 关键字在方法中抛出异常。
Java 中抛出异常的方法有两种,分别是 throw 和 throws,二者的区别在于:
1、
throw 关键字抛出异常对象
throws 关键字指明要抛出给方法调用者的异常
2、
throw 关键字通常用于方法体中,并且抛出一个异常对象
throws 关键字通常被应用在声明方法时,用来指定方法可能抛出的异常,多个异常可使用逗号分隔开
使用 throws 关键字抛出异常
throws 关键字通常被应用在声明方法时,用来指定方法可能抛出的异常。多个异常可使用逗号分隔开
使用 throws 关键字将异常抛给上一级后,如果不想处理该异常,可以继续向上抛出,但最终要有能够处理该异常的代码
注意:如果是 Error、RuntimeException 或它们的子类,可以不使用 throws 关键字来声明要抛出的异常,编译依旧能顺利通过,但在运行时会被系统自动抛出
语法格式:
public void method() throws 异常类型1,异常类型2,....,异常类型n{
}
知识点:
1、抛出异常要慎重,从现在开始养成良好的习惯,能自己处理的异常千万别抛出,自己处理不了的问题一定要抛出去
2、main 方法抛出的异常是由 java 虚拟机去处理的,我们干涉不了,所以在 main 方法中我们尽量用 try catch 去捕获异常,而不要用 throws 去抛出异常
3、如果是 Error、RuntimeException 或它们的子类,可以不使用 throws 关键字来声明要抛出的异常,编译依旧能顺利通过,但在运行时会被系统抛出
4、休眠一秒,一秒之后再继续运行程序
Thread.sleep(1000);
throws 将代码中可能产生的异常交给别人来处理,用于方法之后
如果我们将可能发生的异常加在 throws 的后面,那我们就可以不用再去 try catch 捕获这些异常了
但是如果此方法在外部的代码被调用的时候,则必须对此方法进行异常捕获
try{
method();
}catch(异常类型 e){
e.printStackTrace();
}
package com.zyt.lesson01;
public class Demo {
public static void show() throws InterruptedException,NullPointerException,Exception{
for(int i=0;i<10;i++){
System.out.println(i);
Thread.sleep(1000); //休眠一秒,一秒之后再继续运行程序
}
}
public static void main(String[] args) {
try {
show();
} catch (NullPointerException e){
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
java 虚拟机会自动处理 main 方法所抛出的异常
//在 main 方法中将异常抛出
package com.zyt.lesson01;
public class Demo {
public static void show() throws InterruptedException,NullPointerException,Exception{
for(int i=0;i<10;i++){
System.out.println(i);
Thread.sleep(1000); //休眠一秒,一秒之后再继续运行程序
}
}
public static void main(String[] args) throws InterruptedException,NullPointerException,Exception{
show();
}
}
使用 throw 关键字抛出异常
throw 关键字通常用于方法体中,并且抛出一个异常对象。程序在执行到 throw 语句时立即终止,它后面的语句都不执行。通过 throw 抛出异常后,如果想在上一级代码中来捕获并处理异常,则需要在抛出异常的方法中使用 throws 关键字在方法的声明中指明要抛出的异常;如果要捕捉 throw 抛出的异常,则必须使用 try catch 语句块。
我们来考虑这样一个场景,此时有一个群体的聚会,我们要统计这次聚会的人数,结果由于统计者的疏忽,将总人数写为了-1000
在计算机中用一个整型变量来保存人数时,-1000是合法的,它并不是一个错误
但在实际生活中,人数是不可能等于负数的
也就是说,此时计算机并不能发现这是一个错误
那么我们改如何解决这个问题呢?我们可以将人数为负的这种情况当成一个异常
throw 用于手动制造一个异常对象
语法格式:
throw new 异常类型();
package com.zyt.lesson01;
public class Demo {
public static void main(String[] args){
int count = -100;
try {
if(count < 0){
throw new ArithmeticException("人员数量是负数:"+count);
}
System.out.println("当前统计人数为:"+count);
} catch (ArithmeticException e) {
e.printStackTrace();
System.out.println("捕捉到了异常");
}
}
}
throw 关键字可以更改异常
throw 能在异常发生之前拦截异常,然后将问题交给另外一个异常,如下,将算术异常变为了空指针异常
package com.zyt.lesson01;
public class Demo {
public static void main(String[] args){
try {
int a = 1,b = 0;
if (b==0){
throw new NullPointerException("b等于0,发生异常");
}
int c = a/b;
} catch (NullPointerException e) {
e.printStackTrace();
}
}
}
//此时抛出的是空指针异常而不是算术异常
运行时异常
RuntimeException 是 Exception 的一个子类,经常与其它异常区分开
运行时异常就像它的名字描述的一样,它只有在程序运行的时候,才会发现这种异常
常见的运行时异常
种类 | 说明 |
---|---|
NullPointerException | 空指针异常 |
ArrayIndexOfBoundException | 数组下标越界异常 |
ArithmeticException | 算术异常 |
ArrayStoreException | 数组中包含不兼容的值抛出的异常 |
IllegalArgumentException | 非法参数异常 |
SecurityException | 安全性异常 |
NegativeArraySizeException | 数组长度为负异常 |
异常的使用原则
1、编写异常的时候不要乱抛异常,如果自己能处理的异常,应该主动的处理掉。
2、当你不能解决一个异常时,才把这个异常交给其他人处理
3、如果你将所有的异常全都抛出,你编写的代码将会非常难维护,不仅会埋下巨大的安全隐患,同时也会被 Boss 指责
4、不要忽略捕捉到的异常,既然已经捕捉到这个异常了,就应该及时的、积极的处理掉,而不是当作没看见
5、不要过度的使用异常,虽然通过异常可以增强程序的健壮性,但是如果使用过多不必要的异常处理,可能会影响到程序的执行效率
6、不要使用过于庞大的 try catch 代码块,即在一个 try catch 代码块中放一段几千行的代码去捕捉其中的异常
7、子类抛出的异常不能比父类更高级,如果父类方法中抛出了多个异常,子类覆盖父类方法的同时,也要抛出相同的异常或者是其异常的子类
特例:RuntimeException 异常及其子类不受此原则约束