Java的”异常“超详细讲解
前言:
我们在写代码的时候,经常会遇到错误,并且编译器会给出相关提示 如:
这里就会显示 ArrayIndexOutOfBoundsException
*(数组越界)*错误。并且会提示报错的行号。
同时我们发现 ArrayIndexOutOfBoundsException
是可以点击进去的,点击进去之后会发现是一个JDK自带的类
由此我们可以知道:
在Java中,不同类型的异常,都有与其对应的类来进行描述,也可以说异常也是一种类
但是我们在日常编写项目时,错误是时常发生的。我们作为程序员当然不希望一个项目时不时的崩溃。
在此之前,我们通常对代码可能出现的潜在问题用 条件判断来进行处理:,下面我以一个简略的游戏进行演示:
可以看到当条件一旦增加,代码就会非常臃肿,并且难以后续对功能进行增加和修改。
所以我们必须要学会如何使用,以及处理异常的方式!!
一、异常的分类:
-
Throwable:
是异常体系的顶层类,其派生出两个重要的子类, Error 和 Exception -
Error:
**指的是Java虚拟机无法解决的严重问题,**比如:JVM的内部错误、资源耗尽等,
典型代表:StackOverflowError(栈溢出错误)和OutOfMemoryError -
Exception:
异常产生后程序员可以通过代码进行处理,使程序继续执行。而异常又分为运行时异常和编译时异常;- 编译时异常:
编译无法通过,编译器会进行提醒。也称为受检查异常(Checked Exception) - 运行时异常:
可以通过编译,但是运行后会报错。也称为非受检查异常(Unchecked Exception)
常见的 NullPointerException(空指针异常)、ArrayIndexOutOfBoundsException(数组越界异常)都属于运行时异常。
- 编译时异常:
二、异常的处理:
1.异常的抛出:
在编写程序时,如果程序中出现错误,此时就需要将错误的信息告知给调用者
关键字:
throw
语法:throw new 异常类型(要告知的信息);
代码案例:
public static void func(int[] arrays) {
//如果传递的是一个空数组,抛出异常
if(arrays == null) {
//这里的NullPointerException是Java自带的一个类
throw new NullPointerException("你传递了一个空指针");
}
}
public static void main(String[] args) {
int[] arrays = null;
func(arrays);
}
运行结果:
注意事项
- 异常抛出 必须写在方法内部!!!(由方法来实现抛出)
- 抛出对象必须是Exception或是其子类
- 默认情况下:RuntimeException及其子类可以通过JVM处理
- 异常一旦抛出,其后的代码就不会执行
我们可以看到,在IDEA中,如果异常后还有代码,会直接提示编译错误(因为无法被执行!)
2. 异常声明:
关键字:
throws
语法格式:
修饰符 返回值类型 方法名(参数列表) throws 异常类型1,异常类型2…{
}
如果我们在使用方法是,有编译时异常(编译器警告),可以使用throws来声明这个异常可能会存在,这样就可以通过编译时异常了。
下面我以clone方法为例子:如果不对main函数进行异常声明,会提示clone编译错误(无法确定能否被克隆)
我们在main函数(使用者)后加入声明:throws CloneNotSupportedException
发现编译错误消失,可以正常运行:
class Student implements Cloneable{
public String name;
public int age;
//构造方法:
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class CloneTest {
public static void main(String[] args) throws CloneNotSupportedException {
Student s1 = new Student("张三",18);
Student s2 = (Student) s1.clone();
}
}
注意事项
-
throws必须跟在方法的参数列表之后
-
声明的异常必须是 Exception 或者 Exception 的子类
-
方法内部如果抛出了多个异常,throws之后必须跟多个异常类型,之间用逗号隔开,如果抛出多个异常类型
具有父子关系,直接声明父类即可。
-
调用声明抛出异常的方法时,调用者必须对该异常进行处理,或者继续使用throws抛出
3.异常捕捉:
关键字:
try - catch
语法格式:
try{
// 将可能出现异常的代码放在这里
}catch(要捕获的异常类型 e){
//异常处理内容
}[catch(异常类型 e){
// 对异常进行处理
}finally{
// 此处代码一定会被执行到
}]
解释:
- try { }:
内部放的是可能出现异常的代码块(也有可能没有异常)
如果出现异常,则异常后续的代码均不会被执行,直接跳转到对应异常类的的catch内容
-
catch( ){ }
- ()小括号内是异常类类名 + 实例化的对象名(这里通常习惯用e表示)
- { } 花括号内表示的对应的异常处理内容
- 可以存在多个catch
- 也可以一个catch内写多个异常类型,只需要一个实例化对象名,中间用 | 隔开
(不推荐用此写法,无法判断异常类型!!)
-
finally{ }
finally{}内的代码块,无论异常发生与否,始终会被执行
通常被用于资源的关闭,对资源进行回收。
注意事项:
-
try块内抛出异常位置之后的代码将不会被执行
-
如果抛出异常类型与catch时异常类型不匹配,即异常不会被成功捕获,也就不会被处理,继续往外抛,直到
JVM收到后中断程序----异常是按照类型来捕获的
-
try中可能会抛出多个不同的异常对象,则必须用多个catch来捕获----即多种异常,多次捕获
-
如果异常存在父子类关系,则必须子类在前(catch),父类在后(catch)
因为如果父类在前,子类内容无法被执行,肯定会被父类捕获(catch)
这里就是因为父类 RuntimeException在前,后续的InputMismatchException无法被执行,出现编译错误。 子类在前则只会抛出子类的异常,而不会抛出父类的异常。
- finally中的代码一定会执行的,且是最后执行。一般在finally中进行一些资源清理的扫尾工作
public static int func3() {
Scanner scanner = new Scanner(System.in);
try{
int a = scanner.nextInt();
return a;
} catch (InputMismatchException e) {
//InputMismatchException是错误输入类型异常
// (这里我想要输入的是int,但是输入String就会报此错误)
e.printStackTrace();
System.out.println("发生了InputMismatchException异常");
}finally {
scanner.close();
System.out.println("Scanner资源已经关闭!!!");
return 100;
}
}
public static void main(String[] args) {
int ret = func3();
System.out.println(ret);
}
注意这里,try语句中已经有了return 语句了, 正常情况下输出的结果是你输入的数;
我们进行输入测试:
我们会发现最终return的值是100,即为finally return的值,
这说明:finally不仅一定会执行,而且可以在return之后最后执行。
三、自定义异常类:
既然异常是一个类,那我们肯定也能通过定义类,来定义一个自定义的异常类型。
具体如何定义呢 ?,我们可以仿照已知的异常类,这里是NullPointerException为例子:
其定义方法为:
- 要继承RuntimeException类(异常父类) 或者是 Exception类。
- 重写构造方法并调用父类的构造,用来传递发生错误的字段
下面我以一个简单的登录代码来进行演示:
首先是 登录功能类:
public class Login {
//这里是字段:
public String userName;
public String passWord;
//进行简单的逻辑判断
public void login(String userName,String passWord) {
//如果输入的信息不匹配该类的成员信息,抛出自定义异常!(这里只抛出,不解决)
if(!this.userName.equals(userName)) {
throw new UserNameException("账户名输入错误!!!");
}
if(!this.passWord.equals(passWord)) {
throw new PassWordException("密码输入错误!!!");
}
}
//构造函数
public Login(String userName, String passWord) {
this.userName = userName;
this.passWord = passWord;
}
}
自定义类:
首先是用户名错误的异常类:
public class UserNameException extends RuntimeException {
public UserNameException() {
super();
}
public UserNameException(String userName) {
super(userName);
}
}
其次是密码错误类:
public class PassWordException extends RuntimeException{
public PassWordException() {
}
public PassWordException(String message) {
super(message);
}
}
在主函数中实例化:
public class Main {
public static void main(String[] args) {
try {
Login login = new Login("admin","123123");
login.login("admin","123");
}catch (UserNameException e) {
//打印函数栈信息
e.printStackTrace();
}catch (PassWordException e) {
//打印函数栈信息
e.printStackTrace();
}finally {
System.out.println("finally被执行,程序继续...");
}
}
}
输出结果:显示了自定义的异常类型 PassWordException
四、 总结:
异常的处理流程: