【Java之路】——异常处理
异常
在java中用类的形式对不正常情况进行了描述和封装对象。描述不正常的情况的类,就称为异常类。以前正常流程代码和问题处理代码相结合,现在将正常流程代码和问题处理代码分离,提高阅读性。其实异常就是java通过面向对象的思想将问题封装成了对象,用异常类对其进行描述。不同的问题用不同的类进行具体的描述,比如角标越界,空指针等。问题很多,意味着描述的类也很多,将其共性进行了向上抽取,形成了异常体系,最终问题(不正常情况)就分成了两大类:error和Exception(异常);
概念
-
在运行时期发生的不正常情况
-
异常是一个类,产生异常就是创建异常对象并抛出一个异常对象。
Java处理异常的方式是中断处理。
-
异常指的并不是语法错误,语法错了,编译不通过,不会产生字节码文件,根本不能运行
异常的根类Throwable
特点
- Throwable及其所有的子类都具有可抛性
- 子类的后缀名都是用其父类名作为后缀,阅读性很强
分类
Error
是由jvm抛出的严重性的问题,这种问题发生一般不针对性处理,直接修改程序。
Exception(异常)
-
特点
异常产生后,可以通过代码的方式纠正,使程序继续运行,是必须要处理的。
-
分类:根据在编译时期还是运行时期去检查异常 分为两种。
-
编译时期异常:checked异常
在编译时期,就会检查,如果没有处理异常,则编译失败。(如日期格式化异常)
-
分类:除了特殊子类RuntimeException体系,Exception和其子类都是。
- IOException
- ClassNotFoundException
- CloneNotSupportException
-
-
运行时期异常:runtime异常
直接编译通过,在运行时期,检查异常 。
- RuntimeException和其子类
-
常用方法
-
public void printStackTrace()
- 打印异常的详细信息。
- 包含了异常的类型,异常的原因,还包括异常出现的位置,
-
public String getMessage()
- 获取发生异常的原因。
- 提示给用户的时候,就提示错误原因。
异常的产生过程
下面以角标越界异常(ArrayIndexOutofBoundsException
)为例,代码如下:
public class ExceptionDemo {
public static void main(String[] args) {
int[] arr = { 34, 12, 67 };
int num = ArrayTools.getElement(arr, 4);
System.out.println("num=" + num);
System.out.println("over");
}
}
class ArrayTools {
// 对给定的数组通过给定的角标获取元素。
public static int getElement(int[] arr, int index) {
int element = arr[index];
return element;
}
}
图解
异常发生后,函数就结束,后面的语句就不再访问,在本例中我们自己抛异常,异常信息自定义,代码如下。
class ArrayTools {
// 对给定的数组通过给定的角标获取元素。
public static int getElement(int[] arr, int index) {
if(arr == null)
throw new NullPointerException("数组的引用不能为空"); //throw抛出一个异常对象
if(index >= arr.length)
throw new ArrayIndexOutOfBoundsException("数组越界了:"+index);
if(index < 0)
throw new ArrayIndexOutOfBoundsException("数组的角标不能为负数:"+index);
int element = arr[index];
return element;
}
}
抛出异常
Java程序的执行过程中如出现异常, 会生成一个异常类对象,该异常对象将被提交给Java运行时系统, 这个过程称为抛出(throw)异常。
格式:throw new 异常类名(参数)
例如:
throw new NullPointerException("要访问的arr数组不存在");
throw new ArrayIndexOutOfBoundsException("该索引在数组中不存在,已超出范围");
异常对象的生成:
自动生成:程序运行过程中,虚拟机检测到程序发生了问题,如果在当前代码中没有找到相应的处理程序,就会在后台自动创建一个对应异常类的实例对象并抛出
手动创建:Exception exception = new ClassCastException();创建好的异常对象不抛出对程序没有任何影响,和创建一个普通对象一样
举例:
package java_demo_2.exceptiondemo;
/**
* @ClassName: Demo02_Exception
* @author: benjamin
* @version: 1.0
* @description: TODO
* @createTime: 2019/07/21/15:47
*/
public class Demo02_Exception {
public static void main(String[] args) {
//创建一个数组
int[] arr = {2, 4, 52, 2};
//根据索引找对应的元素
int index = 4;
int element = getElement(arr, index);
System.out.println(element);
System.out.println("over");
}
/*
根据 索引找到数组中对应的元素
*/
public static int getElement(int[] arr, int index) {
//判断 索引是否越界
if (index < 0 || index > arr.length - 1) {
/*
判断条件如果满足,当执行完throw抛出异常对象后,方法已经无法继续运算。
这时就会结束当前方法的执行,并将异常告知给调用者。这时就需要通过异常来解决。
*/
throw new ArrayIndexOutOfBoundsException("哥们,角标越界了~~~");
}
int element = arr[index];
return element;
}
}
运行结果:
那么我们通过throw将异常进行抛出后,也就是将问题返回给调用者后,对用调用者有了两种处理方法,一个是继续将问题声明出去,使用throws声明处理,另一个是捕获处理。
异常处理体系
如果异常出现的话,会立刻终止程序,所以我们得处理异常:
- 在方法不处理,而是声明抛出,由该方法的调用者来处理(throws)。
- 在方法中使用try-catch的语句块来处理异常。
声明异常throws
概念
表示当前方法不处理异常,而是提醒该方法的调用者来处理异常。
格式
修饰符 返回值类型 方法名(参数) throws 异常类名1,异常类名2…{ }
注意:在方法声明中用throws语句可以声明抛出异常的列表, throws后面的异常类型可以是方法中产生的异常类型, 也可以是它的父类。
throw和throws区别
-
throw
- 抛出异常对象
- 使用在函数内
- throw new 异常类名(参数),举例:throw new NullPointerException("要访问的arr数组不存在");
-
throws
- 抛出异常类
- 使用在函数上
- 可以抛多个,用
,
隔开
捕获异常try...catch...
概念
Java中对异常有针对性的语句进行捕获,可以对出现的异常进行指定方式的处理。
格式
try{
编写可能会出现异常的代码
}catch(异常类型 e){
处理异常的代码
//记录日志/打印异常信息/继续抛出异常
}
-
使用finally代码块
- try...catch....finally:自身需要处理异常,最终还得关闭资源。
- 不论在try代码块中是否发生了异常事件, catch语句是否执行, catch语句是否有异常, catch语句中是否有return,finally块中的语句都会被执行
- 当只有在try或者catch中调用退出JVM的相关方法,此时finally才不会执行,否则finally永远会执行
-
注意
多个异常采用一次捕捉多次处理
格式
try{ 编写可能会出现异常的代码 }catch(异常类型A e){ 当try中出现A类型异常,就用该catch来捕获. 处理异常的代码 //记录日志/打印异常信息/继续抛出异常 }catch(异常类型B e){ 当try中出现B类型异常,就用该catch来捕获. 处理异常的代码 //记录日志/打印异常信息/继续抛出异常 }
注意
-
如果父类抛出了多个异常,子类重写父类方法时,抛出和父类相同的异常或者是父类异常的子类或者不抛出异常。
-
父类方法没有抛出异常,子类重写父类该方法时也不可抛出异常。此时子类产生该异常,只能捕获处理,不能声明抛出
-
自定义异常类
概念
对于负数为角标的情况,自己自定义一个用负数角标异常来表示。负数角标这种异常在java中并没有定义过,按照java异常的创建思想,将负数角标进行自定义描述,并封装成对象。这种自定义的问题描述称为自定义异常。
一般地,用户自定义异常类都是RuntimeException的子类。
自定义异常类通常需要编写几个重载的构造器。
自定义异常需要提供serialVersionUID
自定义的异常通过throw抛出。
自定义异常最重要的是异常类的名字,当异常出现时,可以根据名字判断异常类型。
步骤
-
自定义一个异常类XxxException,继承现有的异常类
-
自定义编译期异常 :自定义类并继承于java.lang.Exception
-
自定义运行时期异常:自定义类并继承于java.lang.RuntimeException
-
-
使用到自定义异常的其他类
-
方法上:throws 异常类名;
-
方法内:throw new 异常类名(参数);
-
代码举例:
模拟注册操作,如果用户名已存在,则抛出异常并提示:亲,该用户名已经被注册。
-
定义一个异常类: RegisterException继承与Exceptions
-
模拟登录操作,使用数组模拟数据库中存储的数据,并提供当前注册账号是否尊才方法用于判断。
// 登录异常类 public class RegisterException extends Exception{ public RegisterException() { super(); } public RegisterException(String message) { super(message); } } //自定义一个使用异常的类 class Demo{ //模拟数据库中已存在的账号 private static String[] names = {"admin","tony","benjie"}; public static void main(String[] args) { // 从键盘输入一个名字 Scanner scanner = new Scanner(System.in); System.out.println("请输入一个名字:"); String uname = scanner.next(); // 调用方法 try { checkUserName(uname); } catch (Exception e) { e.printStackTrace(); } } public static boolean checkUserName(String uname) throws Exception{ for(String name: names){ if(name.equals(uname)){ throw new RegisterException("亲"+uname+"已经被注册了!"); } } return true; } }
总结:
异常处理的5个关键字
try:执行可能产生异常的代码
catch:捕获异常
finally:无论是否发生异常,代码总被执行
throw:抛出异常。异常的生成阶段,手动抛出异常对象。throw new 异常类名(参数),例如:throw new NullPointerException("要访问的数组不存在");
throws:声明异常。异常的处理方式,声明方法可能要抛出的各种异常类。