续接上一讲
目录
一、异常的处理(续)
1、异常的捕获-try-catch捕获并处理异常
1.1关于异常的处理方式
2、finally
3、异常的处理流程
二、自定义异常类
1、实现自定义异常类
一、异常的处理(续)
1、异常的捕获-try-catch捕获并处理异常
throws对异常并没有真正处理,而是将异常报告给抛出异常方法的调用者,由调用者处理。如果真正要对异常进行处理,就需要try-catch。
语法格式:
try{// 将可能出现异常的代码放在这⾥
}catch(要捕获的异常类型e){// 如果try中的代码抛出异常了,此处catch捕获时异常类型与try中抛出的异常类型⼀致时,或者是try中抛出异常的基类时,就会被捕获到 // 对异常就可以正常处理,处理完成后,跳出try-catch结构,继续执⾏后序代码
}[catch(异常类型 e){// 对异常进⾏处理
}finally{// 此处代码⼀定会被执⾏到
}]// 后序代码
// 当异常被捕获到时,异常就被处理了,这⾥的后序代码⼀定会执⾏
// 如果捕获了,由于捕获时类型不对,那就没有捕获到,这⾥的代码就不会被执⾏注意:
1. []中表⽰可选项,可以添加,也可以不⽤添加
2. try中的代码可能会抛出异常,也可能不会
例:读取配置文件,如果配置文件名字不是指定名字,抛出异常,调用者进行异常处理
public class Config {File file;public void openConfig(String filename) throws FileNotFoundException{if(!filename.equals("config.ini")){throw new FileNotFoundException("配置⽂件名字不对");}// 打开⽂件 }public void readConfig(){}public static void main(String[] args) {Config config = new Config();try {config.openConfig("config.txt");System.out.println("⽂件打开成功");} catch (IOException e) {// 异常的处理⽅式//System.out.println(e.getMessage()); // 只打印异常信息 //System.out.println(e); // 打印异常类型:异常信息 e.printStackTrace(); // 打印信息最全⾯}// ⼀旦异常被捕获处理了,此处的代码会执⾏ System.out.println("异常如果被处理了,这⾥的代码也可以执⾏");}}
1.1关于异常的处理方式
异常的种类有很多,我们要根据不同的业务场景来决定:
(1)对于比较严重的问题(如算钱),应该直接让程序崩溃,减小损失
(2)对于不太严重的问题,可以记录错误日志,通过监控报警程序及时通知告知程序猿
(3)对于可能会恢复的问题(网络相关场景),可以尝试重试
在我们当前的代码中采取的是经过简化的第⼆种方式.我们记录的错误日志是出现异常的方法调用信息,能很快速的让我们找到出现异常的位置.以后在实际工作中我们会采取更完备的方式来记录异常信息
#注:
(1)try块内抛出异常位置之后的代码将不会被执行
(2)如果抛出异常类型与catch时异常类型不匹配,即异常不会被成功捕获,也就不会被处理,继续往外抛,直到JVM收到后中断程序----异常是按照类型来捕获的
public static void main(String[] args) {try {int[] array = {1,2,3};System.out.println(array[3]); // 此处会抛出数组越界异常 }catch (NullPointerException e){ // 捕获时候捕获的是空指针异常--真正的异常⽆法被捕获到 e.printStackTrace();}System.out.println("后续代码");}Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 3at day20210917.ArrayOperator.main(ArrayOperator.java:24)
(3)try中可能会抛出多个不同的异常对象,则必须用多个catch来捕获----即多种异常,多次捕获
public static void main(String[] args) {int[] array = {1, 2, 3};try {System.out.println("before");// array = null;System.out.println(array [100]);System.out.println("after");} catch (ArrayIndexOutOfBoundsException e) {System.out.println("这是个数组下标越界异常");e.printStackTrace();} catch (NullPointerException e) {System.out.println("这是个空指针异常");e.printStackTrace();}System.out.println("after try catch");}
如果多个异常完全相同,也可以这样写:
catch (ArrayIndexOutOfBoundsException | NullPointerException e) {...}
如果异常之间具有父子关系,⼀定是子类异常在前catch,父类异常在后catch,否则语法错误:
public static void main(String[] args) {int[] arr = {1, 2, 3};try {System.out.println("before");arr = null;System.out.println(arr[100]);System.out.println("after");} catch (Exception e) { // Exception可以捕获到所有异常 e.printStackTrace();}catch (NullPointerException e){ // 永远都捕获执⾏到 e.printStackTrace();}System.out.println("after try catch");}Error:(33, 10) java: 已捕获到异常错误java.lang.NullPointerException
(4)可以通过⼀个catch捕获所有的异常,即多个异常,⼀次捕获(不推荐)
public static void main(String[] args) {int[] arr = {1, 2, 3};try {System.out.println("before");arr = null;System.out.println(arr[100]);System.out.println("after");} catch (Exception e) {e.printStackTrace();}System.out.println("after try catch");}
由于Exception类是所有异常类的父类.因此可以用这个类型表示捕捉所有异常.
#注:catch进行类型匹配的时候,不光会匹配相同类型的异常对象,也会捕捉目标异常类型的子类对 象
2、finally
在写程序时,有些特定的代码,不论程序是否发⽣异常,都需要执行,比如程序中打开的资源:网络连接、数据库连接、IO流等,在程序正常或者异常退出时,必须要对资源进进行回收。另外,因为异常会引发程序的跳转,可能导致有些语句执行不到,finally就是用来解决这个问题的。
语法格式:
try{// 可能会发⽣异常的代码
}catch(异常类型 e){// 对捕获到的异常进⾏处理
}finally{// 此处的语句⽆论是否发⽣异常,都会被执⾏到
}// 如果没有抛出异常,或者异常被捕获处理了,这⾥的代码也会执⾏
public static void main(String[] args) {try{int[] array = {1,2,3};array [100] = 10;array [0] = 10;}catch (ArrayIndexOutOfBoundsException e){System.out.println(e);}finally {System.out.println("finally中的代码⼀定会执⾏");}System.out.println("如果没有抛出异常,或者异常被处理了,try-catch后的代码也会执⾏");}
#注:finally中的代码⼀定会执行的,⼀般在finally中进行⼀些资源清理的扫尾工作,如果异常被捕获,那么将不会继续执行后续的内容,而是直接走完当前捕获异常的catch后直接走finally(除了finally其他的都不执行)
finally 执行的时机是在方法返回之前(try或者catch中如果有return会在这个return之前执行 finally). 但是如果finally 中也存在return语句,那么就会执行finally中的return,从而不会执行到try 中原有的return.
⼀般我们不建议在finally中写return(会被编译器当做⼀个警告)
3、异常的处理流程
关于“调用栈”:
方法之间是存在相互调用关系的,这种调用关系我们可以"调用栈"来描述.在JVM中有⼀块内存空间称为"虚拟机栈",专门存储方法之间的调用关系.当代码中出现异常的时候,我们就可以使用 e.printStackTrace(); 的方式查看出现异常代码的调用栈.
如果本方法中没有合适的处理异常的方式,就会沿着调用栈向上传递
public static void main(String[] args) {try {func();} catch (ArrayIndexOutOfBoundsException e) {e.printStackTrace();}System.out.println("after try catch");}public static void func() {int[] array = {1, 2, 3};System.out.println(array[100]);}//结果java.lang.ArrayIndexOutOfBoundsException: Index 100 out of bounds for length 3at LibrarySystem.func(LibrarySystem.java:107)at LibrarySystem.main(LibrarySystem.java:98)after try catch
如果向上⼀直传递都没有合适的方法处理异常,最终就会交给JVM处理,程序就会异常终止(和我们最开始未使用trycatch时是⼀样的)
异常处理流程总结:
(1)程序先执行try中的代码
(2)如果try中的代码出现异常,就会结束try中的代码,看和catch中的异常类型是否匹配
(3)如果找到匹配的异常类型,就会执行catch中的代码
(4)如果没有找到匹配的异常类型,就会将异常向上传递到上层调用者
(5)无论是否找到匹配的异常类型,finally中的代码都会被执行到(在该方法结束之前执行)
(6)如果上层调用者也没有处理的了异常,就继续向上传递
(7)⼀直到main方法也没有合适的代码处理异常,就会交给JVM来进行处理,此时程序就会异常终止
二、自定义异常类
Java 中虽然已经内置了丰富的异常类,但是并不能完全表示实际开发中所遇到的⼀些异常,此时就需要维护符合我们实际情况的异常结构
例如,我们实现⼀个用户登陆功能:
public class LogIn {private String userName = "admin";private String password = "123456";public void loginInfo(String userName, String password) {if (!this.userName.equals(userName)) {System.out.println("⽤⼾名错误!");return;}if (!this.password.equals(password)) {System.out.println("密码错误!");return;}System.out.println("登陆成功");}public static void main(String[] args) {LogIn logIn = new LogIn();logIn.loginInfo("admin111", "123456");}}
此时我们在处理用户名密码错误的时候可能就需要抛出两种异常.我们可以基于已有的异常类进行扩展 (继承),创建和我们业务相关的异常类
1、实现自定义异常类
具体方式:
(1)自定义异常类,然后继承自Exception或者RunTimeException
(2)实现⼀个带有String类型参数的构造方法,参数含义:出现异常的原因
class UserNameException extends Exception {public UserNameException(String message) {super(message);}}class PasswordException extends Exception {public PasswordException(String message) {super(message);}}
此时我们的LogIn代码可以改成:
public class LogIn {private String userName = "admin";private String password = "123456";public void loginInfo(String userName, String password) throws UserNameException,PasswordException{if (!this.userName.equals(userName)) {throw new UserNameException("用户名错误!");}if (!this.password.equals(password)) {throw new PasswordException("用户名错误!");}System.out.println("登陆成功");}public static void main(String[] args) {try {LogIn login = new LogIn();login.loginInfo("admin", "123456");} catch (UserNameException e) {e.printStackTrace();} catch (PasswordException e) {e.printStackTrace();}}}
#注:
(1)自定义异常通常会继承自Exception或者RuntimeException
(2)继承自Exception的异常默认是受查异常
(3)继承自RuntimeException的异常默认是非受查异常