在Java中,将程序执行过程中发生的不正常行为称为异常 

 比如以下几个异常

1. 算术异常 

因为 0 不能当被除数,所以报出了异常,这种异常就叫作算数异常 

2.空指针异常 

 

3.数组越界异常 

  

4.在编译时就发现了异常 

那有的异常在编译时就被发现,有的要在运行之后才发现,

那我们接下来就要了解一下异常的体系结构
 

2.异常的体系结构
 

Java内部维护了一个异常的体系结构:如下
 

 

 

我们先看一下前三个 

 

IDEA小贴士:双击shift ,出现下面界面

利用上面的工具,我们明确看出: (图片看不清可以下载到本地,就看清了╰(*°▽°*)╯

Error:指的是Java虚拟机无法解决的严重问题,比如:JVM的内部错误、资源耗尽等,

            典型代表:StackOverflowError(栈溢出)和OutOfMemoryError(堆溢出) 

写个递归看一下栈溢出 

  

 

 总结:

Exception:异常产生后程序员可以通过代码进行处理,使程序继续执行。我们平时所说的异常就是Exception(具体类型参考异常的体系结构图下面那一堆)
Error:指的是Java虚拟机无法解决的严重问题,比如:JVM的内部错误、资源耗尽等,典型代表:StackOverflowError(栈溢出)和OutOfMemoryError(堆溢出)

ps: CloneNotSupported处理过程

 

3.异常的分类 

编译时异常(也称受查异常) (比如上面的CloneNotSupportedException,只要不处理就报错)
运行时异常(也称非受查异常(Unchecked Exception))(RunTimeException以及其子类对应的异常,都称为运行时异常,比如上述的数组越界,空指针,算数异常等等)

注意:编译时出现的语法性错误,不能称之为异常。(比如拼写错误,没加;等等,此时编译过程中就会出错, 这是 "编译期" 出错。而运行时指的是程序已经编译通过得到class 文件了, 再由 JVM 执行过程中出现的错误)
 

 

4.异常的处理
1 .防御式编程

1.LBYL: Look Before You Leap. 在操作之前就做充分的检查. 即:事前防御型

2. EAFP: It's Easier to Ask Forgiveness than Permission. "事后获取原谅比事前获取许可更容易". 也就是先操作, 遇到问题再处理. 即:事后认错型

2. 事后认错型

语法:

try{

执行可能出现异常的代码

}catch(){

匹配(对应的异常)

} finally{

执行 资源的关闭

}

优势:正常流程和错误流程是分离开的, 程序员更关注正常流程,代码更清晰,容易理解代码
异常处理的核心思想就是 EAFP

在Java中,异常处理主要的5个关键字:throw、try、catch、final、throws。

2.异常的抛出

在Java中,可以借助throw关键字,抛出一个指定的异常对象,将错误信息告知给调用者。具体语法如下:

throw new XXXException("异常产生的原因");

ps:一般用来抛出一个自定义的异常
1. throw必须写在方法体内部
2. 抛出的对象必须是Exception 或者 Exception 的子类对象
3. 如果抛出的是 RunTimeException 或者 RunTimeException 的子类,则可以不用处理, 直接交给JVM来处理
4. 如果抛出的是编译时异常,用户必须处理,否则无法通过编译
5. 异常一旦抛出,其后的代码就不会执行

编译时异常,用户必须处理,最简单的方法是通过 throws 和 try-catch 去处理,接下来我们看一下如何处理

3 .异常的捕获
异常的捕获,也就是异常的具体处理方式,主要有两种:异常声明throws 以及 try-catch捕获处理。

3.1 .异常声明throws

处在方法声明时参数列表之后,当方法中抛出编译时异常,用户不想处理该异常,此时就可以借助throws将异常抛给方法的调用者来处理。即当前方法不处理异常,提醒方法的调用者处理异常。

代码解释 :

 

test()方法里只是抛出了异常,而没有处理这个异常,而main方法是test()方法的调用者,他要处理test()方法抛出的这个异常,没处理就报错 ,如果在main()方法后再加一个throws,不报错了,但是main()方法依旧没处理,只是把这个异常又抛了出去,那交给谁处理了呢?JVM,因为JVM调用main()方法
 

 

1. throws必须跟在方法的参数列表之后
2. 声明的异常必须是 Exception 或者 Exception 的子类
3. 方法内部如果抛出了多个异常,throws之后必须跟多个异常类型,之间用逗号隔开,如 果抛出多个异常类型具有父子关系,直接声明父类即可

throw和throws的区别?
throws声明这个方法可能抛出什么异常,

throw是可以抛出什么异常

我们不仅可以利用 throws,还可以利用try-catch 处理,那接下来我们来介绍 一下 try-catch

 

3.2.try-catch捕获并处理

语法:

 

try{

执行可能出现异常的代码

}catch(要捕获的异常类型 e){

匹配(对应的异常)

// 如果try中的代码抛出异常了,此处catch捕获时异常类型与try中抛出的异常类型一致时, 或者是try中抛出异常的父类时,就会被捕获到
// 对异常就可以正常处理,处理完成后,跳出try-catch结构,继续执行后序代码

}catch(异常类型 e){

// 对异常进行处理

} finally{

执行 资源的关闭

// 此处代码一定会被执行到

}

// 后序代码
// 当异常被捕获到时,异常就被处理了,这里的后序代码一定会执行
// 如果捕获了,由于捕获时类型不对,那就没有捕获到,这里的代码就不会被执行

 

 那如何利用 try-catch捕获并处理?如下:

 

 

 1.catch一定要捕获一个对应匹配的异常 ,否则还是会交给JVM处理

 

 

 那找异常好麻烦的,有聪明的小伙伴可能就会想到直接用Exception e,不建议哦,请老老实实的写👈(⌒▽⌒)👉👈(⌒▽⌒)👉 

 

2.那我再try里面异常代码下再写一个正确的代码,它会执行吗?不会。 

 

 

 

写上面就执行了 

 

 

因为是从异常那句发现了发生了异常,说明这个业务从这是有问题的,所以下面的代码将不会被执行 。

3.try中可能会抛出多个不同的异常对象,则必须用多个catch来捕获----即多种异常,多次捕获 

 

 

 

 

 

有上可知,程序只会同时抛出一个异常,先发现谁,就抛出谁,不会抛出多个异常 

上面的异常还能合成一个,但不建议,还是比较模糊

 

我们现在发现了异常,但是不知道哪里有异常,我们就可以利用 e.printStackTrace(); 去定位

(e.printStackTrace(); 可以帮我们打印栈上的异常信息)

 

 

5.如果异常之间具有父子关系,一定是子类异常在前catch,父类异常在后catch,否则语法错误:
 

父类兜底,子债父偿 🙈🙈

 

 

 

 

3.3 finally
在写程序时,有些特定的代码,不论程序是否发生异常,都需要执行,比如程序中打开的资源:网络连接、数据库连接、IO流等,在程序正常或者异常退出时,必须要对资源进进行回收。另外,因为异常会引发程序的跳转,可能导致有些语句执行不到,finally就是用来解决这些问题的

代码演示:

 

 

当catch捕获到了异常,finally执行了  

当catch没捕获到异常时finally 也执行了

所以,不管是否抛出异常,finally 都被执行了,

那一些善后的代码就可以放到这里,比如,关闭资源等等

我们看一下下面的代码: 

 

 

虽然执行了,但是我们不建议在finally里return 。

4 .异常的处理流程
 

 

在func2()这里我没有处理,在main()这里我没有处理异常,那异常交给JVM处理了 

 

那你也可以自己处理异常 

在func2()处理 

 

或者让func2()的调用者main处理 

 

 

如果向上一直传递都没有合适的方法处理异常, 最终就会交给 JVM 处理, 程序就会异常终止(和我们最开始未使用 try-catch 时是一样的)

🙇🙇【异常处理流程总结】 🙇🙇
程序先执行 try 中的代码
如果 try 中的代码出现异常, 就会结束 try 中的代码, 看和 catch 中的异常类型是否匹配.
如果找到匹配的异常类型, 就会执行 catch 中的代码
如果没有找到匹配的异常类型, 就会将异常向上者传递到上层调用.
无论是否找到匹配的异常类型, finally 中的代码都会被执行到(在该方法结束之前执行).
如果上层调用者也没有处理的了异常, 就继续向上传递.一直到 main 方法也没有合适的代码处理异常, 就会交给 JVM 来进行处理, 此时程序就会异常终止
3. 自定义异常类
我们看下面的代码,我们可以想到,用户名或密码都是可能出现异常的地方,但是java里没有定义这个异常,那此时我们就要自定义异常

 

那我们就自己创一个 UserNameException类,

我们自己定义的异常时可以参考java原本的异常,那我们就参考 ArithmeticException去写

代码:

 

public class PasswordException extends RuntimeException {

public PasswordException() {
}

public PasswordException(String message) {
super(message);
}
}
public class UserNameException extends RuntimeException{

public UserNameException() {
}

public UserNameException(String message) {
super(message);
}
}
public class Login {
private String userName = "admin";
private String password = "12345";
//可能会抛出这俩异常
public void loginInfo(String userName, String password) throws UserNameException,PasswordException{
if(!this.userName.equals(userName)){
//System.out.println("用户名有误");
throw new UserNameException("用户名有误");
}
if(!this.password.equals(password)){
//System.out.println("密码有误");
throw new PasswordException("密码有误");
}
System.out.println("登录成功");
}

public static void main(String[] args) {

Login login = new Login();
try {
login.loginInfo("admin1", "12345");
}catch (UserNameException e){
e.printStackTrace();//定位异常
System.out.println("UserNameException");

}catch (PasswordException e){
e.printStackTrace();
System.out.println("PasswordException");
}
}
}
上面是继承RuntimeException是非受查异常,

那继承Exception呢,会报错这个异常就变成了受查异常

//Exception

 

 

 

//RuntimeException 

 

 


总结:
自定义异常通常会继承自 Exception 或者 RuntimeException
继承自 Exception 的异常默认是受查异常
继承自 RuntimeException 的异常默认是非受查异常

 

面试题:

请写出你最常见的 几 个 RuntimeException
1)java.lang.NullPointerException 空指针异常;出现原因:调用了未经初始化的对象或者是不存在的对象。
2)java.lang.ClassNotFoundException 指定的类找不到;出现原因:类的名称和路径加载错误;通常都是程序
试图通过字符串来加载某个类时可能引发异常。
3)java.lang.NumberFormatException 字符串转换为数字异常;出现原因:字符型数据中包含非数字型字符。
4)java.lang.IndexOutOfBoundsException 数组角标越界异常,常见于操作数组对象时发生。
5)java.lang.IllegalArgumentException 方法传递参数错误。
6)java.lang.ClassCastException 数据类型转换异常。7)java.lang.NoClassDefFoundException 未找到类定义错误。
8)SQLException SQL 异常,常见于操作数据库时的 SQL 语句错误。
9)java.lang.InstantiationException 实例化异常。
10)java.lang.NoSuchMethodException 方法不存在异常。
 
6. throw 和 throws 的区别
throw:
1)throw 语句用在方法体内,表示抛出异常,由方法体内的语句处理。
2)throw 是具体向外抛出异常的动作,所以它抛出的是一个异常实例,执行 throw 一定是抛出了某种异常。
throws:
1)throws 语句是用在方法声明后面,表示如果抛出异常,由该方法的调用者来进行异常的处理。
2)throws 主要是声明这个方法会抛出某种类型的异常,让它的使用者要知道需要捕获的异常的类型。
3)throws 表示出现异常的一种可能性,并不一定会发生这种异常。
 
7. final、finally、finalize 的区别?(2017-11-23-wzz)
1)final:用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,被其修饰的类不可继承。
2)finally:异常处理语句结构的一部分,表示总是执行。
3)finalize:Object 类的一个方法,在垃圾回收器执行的时候会调用被回收对象的此方法,可以覆盖此方法
提供垃圾收集时的其他资源回收,例如关闭文件等。该方法更像是一个对象生命周期的临终方法,当该方法
被系统调用则代表该对象即将“死亡”,但是需要注意的是,我们主动行为上去调用该方法并不会导致该对
象“死亡”,这是一个被动的方法(其实就是回调方法),不需要我们调用。


原文链接:https://blog.csdn.net/iiiiiihuang/article/details/130671801