7.Throwable
7.1.概念
- 异常 :指的是程序在执行过程中,出现的非正常的情况,最终会导致JVM的非正常停止。
在Java等面向对象的编程语言中,异常本身是一个类,产生异常就是创建异常对象并抛出了一个异常对象。Java处理异常的方式是中断处理。 - 异常的根类是
java.lang.Throwable
,其下有两个子类:java.lang.Error
与java.lang.Exception
,平常所说的异常指java.lang.Exception
。 - Throwable体系:
- Error:严重错误Error,无法通过处理的错误,只能事先避免。
- Exception:表示异常,异常产生后程序员可以通过代码的方式纠正,使程序继续运行,是必须要处理的。
一些个人理解:
- 异常处理的根本目的,还是想让程序继续运行下去,比如,某一块代码出现了异常,但是我想看一下后面的代码运行情况,就可以先处理掉异常,让后面的程序继续运行。
- 某种情况下,用户输入的数据不合法,而我们的程序并不能中断,仍然要继续执行下去,因此我们可以throw一个异常,并在调用方法中处理这个异常,使程序继续执行。
- 异常处理有两种方法,throws和try…catch,如果我们用throws持续向上抛出,最后交给JVM,一样会被中断处理(只有极少数抛出异常不会中断程序的运行,比如CloneNotSupportedException),结果到最后还是要try…catch来处理,因为我们想让程序继续运行下去,否则程序一定会被JVM中断。但是这里有个特殊情况,对于编译时期的异常,如果我们不处理,程序是没有办法Run的,所以编译时期的异常我们必须及时来处理。
- 为什么main方法抛出CloneNotSupportedException异常,JVM没有中断程序呢?因为,并没有出现异常,throws声明在方法名称后,只是说,可能会出现异常,告诉程序员做好处理的准备,同时,目的也是解决编译期异常,否则程序无法Run,这是clone()所必需的规定,而对于大多数情况来说,throws一个异常,是确定了会抛出一个异常,因此我们应该尽力避免main方法抛出异常,最迟要在main方法自己捕捉异常。
7.2.常用方法
public void printStackTrace()
:打印异常的详细信息。异常的类型,异常的原因,还包括异常出现的位置,在开发和调试阶段,都得使用printStackTrace。public String getMessage()
:获取发生异常的原因。提示给用户的时候,就提示错误原因。public String toString()
:获取异常的类型和异常描述信息。printStackTrace(PrintStream s)
:使用IO流,将异常内容保存在日志文件中,以便查阅(过时的日志处理方式)
7.3.异常分类
- 异常(Exception)的分类:根据在编译时期还是运行时期去检查异常
- 编译时期异常:checked异常。在编译时期,就会检查,如果没有处理异常,则编译失败。(如日期格式化异常)
- 运行时期异常:runtime异常。在运行时期,检查异常.在编译时期,运行异常不会编译器检测(不报错)。(如数学异常)
7.4.异常处理
- Java中,异常处理的机制有两种,自己解决或者向上抛出,即某个方法发生了异常,如果该方法解决不了,那么就向上抛给调用自己的方法,直到main方法也解决不掉后,那么就抛给JVM处理,JVM会中断程序,将异常打印在控制台上。中断后的语句都无法执行。
7.4.1.抛出异常throw
- 在java程序中,我们需要判断用户输入数据的合法性,等等,因此,Java提供了一个throw关键字,它用来抛出一个指定的异常对象。
- 创建一个异常对象。封装一些提示信息(信息可以自己编写)。
- 需要将这个异常对象告知给调用者。怎么告知呢?怎么将这个异常对象传递到调用者处呢?通过关键字throw就可以完成。throw 异常对象。
throw用在方法内,用来抛出一个异常对象,将这个异常对象传递到调用者处,并结束当前方法的执行。
throw new 异常类名(参数);
//例如:
throw new NullPointerException("要访问的arr数组不存在");
throw new ArrayIndexOutOfBoundsException("该索引在数组中不存在,已超出范围");
//throw异常 举例
public class ThrowDemo {
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;
}
}
7.4.2.声明异常throws
- 声明异常:将问题标识出来,报告给调用者。如果方法内通过throw抛出了编译时异常,而没有捕获处理(捕获处理在笔记后边),那么必须通过throws进行声明,让调用者去处理。
- 关键字throws运用于方法声明之上,用于表示当前方法不处理异常,而是提醒该方法的调用者来处理异常(抛出异常)。
- 运行时异常会自动向上抛出,不用我们手动throws
- 我们只需要手动throws编译时异常
- 如果方法抛出一个编译时异常,可以在语法层面,强制要求方法调用者处理该异常
- 异常列表可以是多个异常类,但是注意用逗号隔开
- 列表中出现的异常如果有父子关系,那么编译器只会强制要求处理父类,所以尽量抛出同级别的异常
- 运行时异常会自动向上抛出,不用我们手动throws
//修饰符 返回值类型 方法名(参数) throws 异常类名1,异常类名2…{ }
public class ThrowsDemo {
public static void main(String[] args) throws IOException {
read("a.txt");
}
public static void read(String path)throws FileNotFoundException, IOException {
if (!path.equals("a.txt")) {//如果不是 a.txt这个文件
//如果不是a.txt这个路径,那么我认为该文件不存在,假设它是一个异常。
throw new FileNotFoundException("文件不存在");
}
if (!path.equals("b.txt")) {
throw new IOException();
}
}
}
7.4.3.捕获异常
- 即使用try…catch关键字来捕获异常,那么就会有两种情况:
- 若匹配成功,catch中写的异常对象就会接收JVM抛出的异常对象,程序未被JVM中断,仍然能够继续执行。
- 若匹配失败,那么程序依然会自动向上抛出异常,直到JVM默认处理
- 单分支try…catch语法如下:
try {
//可能出现异常的,正常的代码逻辑
} catch(要捕捉的异常对象) {
//每一个catch分支对应一个异常处理器
//在catch分支中处理具体类型的代码异常
}
//其中catch的括号里可以添加多个异常类型,但是不建议这么做
//无论能够匹配多少种异常类型,始终都只有一个异常对象被接收,对象名只写一个
catch(要捕捉的异常类型1 | 要捕捉的异常对类型2 | 要捕捉的异常类型3 对象名...){}
注:
try代码块中某个位置产生了异常,那么try中的代码就不继续执行了,也就是说try当中要么不产生异常,要么只会产生一个异常。
- 多分支try…catch语法如下:
try {
//可能出现异常的,正常的代码逻辑
} catch(要捕捉的异常对象1) {
//每一个catch分支对应一个异常处理器
//在catch分支中处理具体类型的代码异常
}catch(要捕捉的异常对象2) {
//每一个catch分支对应一个异常处理器
//在catch分支中处理具体类型的代码异常
}
- 对于多分支语法,匹配流程如下:
- 根据实际的异常对象的类型,和catch中声明的异常类型,从上到下一次做类型匹配。
- 一旦通过类型匹配,发现实际异常对象的类型和catch中的异常对象类型匹配,就把该异常对象交给这个catch分支进行处理(异常处理器)。
- 没有相匹配catch代码块的异常,那么程序依然会自动向上抛出异常,直到 JVM 默认处理。
一些多分支匹配的注意事项:
- 多分支的异常处理的执行,有点类似于多分支if-else的执行,一次匹配,只会执行多个catch分支中的一个
- 如果多个catch中处理的是毫无关系的异常,那么catch的顺序并不需要特别注意
- 如果多个catch中处理的异常有父子关系,那么就必须要注意了
- 如果父类异常写在了上面,那么子类异常的catch分支就永远没有机会执行了,并且会报错
- 应该把具体子类放在catch分支的上面作类型匹配,父类放在后面作兜底
- 另外,如果子类在上边,那么仍然会执行父类的异常语句
- catch()括号当中,尽量不要写Exception这种大而宽泛的异常,而是应该写具体的异常,越具体越好,这样对处理异常有帮助,能够清晰定位异常的类型
- try…catch…finally语句
- finally的特点
- 无论try中是否发生异常,都会执行
- try-catch代码中有return也不能阻止它
- 特殊情况:在执行到finally之前jvm退出了:System.exit(0)
- finally的特点
一些finally语句的注意事项:
finally一般用于释放资源,常用于IO流操作和数据库操作中。
若try代码块里有return语句,那么应仍然应该先执行finally语句,再继续执行return。
如果finally中没有return,那么执行完finally后再回到try中执行return语句。
如果finally中也有return,那么执行完finally语句中的return后,不会再回到try中。
执行完return语句后,返回值无论怎么修改,都不会改变打印结果。
- Example:
//方法名(形参列表) throws 异常列表{}
public class TryCatchDemo {
public static void main(String[] args) {
try {
read("a.txt");
} catch (FileNotFoundException e) {
//抓取到的是编译期异常 抛出去的是运行期
throw new RuntimeException(e);
} finally {
System.out.println("不管程序怎样,这里都将会被执行。");
}
System.out.println("over");
}
/*
*
* 我们当前的这个方法中,有编译期异常
*/
public static void read(String path) throws FileNotFoundException {
if (!path.equals("a.txt")) {//如果不是 a.txt这个文件
//如果不是a.txt这个路径,那么我认为该文件不存在,假设它是一个异常。
throw new FileNotFoundException("文件不存在");
}
}
}
7.5.自定义异常类
- 在开发中根据自己业务的异常情况来定义异常类,比如年龄负数问题,考试成绩负数问题,等等。
- 继承自Exception:编译时要检查
- 继承自RuntimeException:编译不需要检查
// 业务逻辑异常
public class RegisterException extends Exception {
/**
* 空参构造
*/
public RegisterException() {}
/**
*
* @param message 表示异常提示
*/
public RegisterException(String message) {
super(message);
}
}
//模拟登陆操作,使用数组模拟数据库中存储的数据,并提供当前注册账号是否存在方法用于判断。
public class Demo {
// 模拟数据库中已存在账号
private static String[] names = {"bill","hill","jill"};
public static void main(String[] args) {
//调用方法
try{
// 可能出现异常的代码
checkUsername("nill");
System.out.println("注册成功");//如果没有异常就是注册成功
}catch(RegisterException e){
//处理异常
e.printStackTrace();
}
}
//判断当前注册账号是否存在
//因为是编译期异常,又想调用者去处理 所以声明该异常
public static boolean checkUsername(String uname) throws RegisterException {
for (String name : names) {
if(name.equals(uname)){//如果名字在这里面 就抛出登陆异常
throw new RegisterException("亲"+name+"已经被注册了!");
}
}
return true;
}
}
7.6.方法覆盖中的异常列表匹配问题
-
首先,异常说明属于方法声明的一部分,紧跟在形式参数列表之后,方法的声明中加了throws关键字表示所有要抛出的潜在异常类型后,方法在重写的时候也会发生一些变化。
-
总体上的原则是:子类中的覆盖方法,不能比父类中的方法抛出更多异常
-
如果子父类方法,完全抛出相同的异常,允许进行方法的重写
-
如果父类方法没有抛出异常,子类重写方法,要么也不抛出异常,要么就只能抛出运行时异常(本身就是自动的,不算多)
-
如果父类方法抛了异常,那么
- 子类重写方法可以选择完全不抛出异常
- 如果父类方法抛出的是RuntimeException,那么子类重写方法也只能抛出RuntimeException
- 种类不限制,允许类型不同
- 父类方法抛出一个RuntimeException子类,子类方法重写可以是RuntimeException
- 如果父类方法抛出的是编译时异常,那么子类重写方法
- 可以抛出相同的编译时异常,但不能抛出不同的编译时异常
- 抛出所有运行时异常
- 不可以抛出Exception
- 如果父类方法直接抛出Exception
- 那么子类重写方法就可以抛出任何异常了
我们其实并不需要特别记忆这些规则,实际开发中,我们并不是像老师上课一样,需要一点不能出错。我们可以不断的尝试,然后最终提交出一份正确的代码。这样,我们仍然是一名优秀的Java开发工程师。如果面试中被问到,建议直接说子类中的覆盖方法,不能比父类中的方法抛出更多异常即可。
建议在开发中,子父类重写方法拥有一致的抛出异常列表,避免自找麻烦。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/181083.html