Java SE 异常
文章目录
1. 异常介绍
1.1 异常的概念
在Java中,将程序执行过程中发生的不正常行为称为异常。
代码示例:
-
算术异常
System.out.println(10/0); //执行结果 Exception in thread "main" java.lang.ArithmeticException: / by zero
-
数组越界异常
int[] arr = {1,2,3}; System.out.println(arr[101]); //执行结果 Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 101
-
空指针异常
int[] arr = null; System.out.println(arr.length); //执行结果 Exception in thread "main" java.lang.NullPointerException
从上述代码中可以看到,Java中不同类型的异常,都有与其对应的类来进行描述
1.2 异常的体系结构
异常种类繁多,为了对不同异常或者错误进行很好的分类管理,Java内部维护了一个异常的体系结构:
从上图中可以看到:
-
Throwable:是异常体系的顶层类,其派生出两个重要的子类,
Error
和Exception
-
Error:指的是Java虚拟机无法解决的严重问题,比如JVM的内部错误、资源耗尽等,典型代表有StackOverflowError和OutOfMemoryError
-
Exception: 异常产生后程序员可以通过代码进行处理,使程序继续执行
1.3 异常的分类
根据异常发生的时机不同,可以将异常分为:
-
编译时异常
在程序编译期间发生的异常,称为编译时异常,也称为受检查异常(Checked Exception)
代码示例:
package demo2; public class Person { private String name; private String gender; int age; public Person clone() { return (Person) super.clone(); } public static void main(String[] args) { Person p = new Person(); } } //编译时异常 java: 未报告的异常错误java.lang.CloneNotSupportedException; 必须对其进行捕获或声明以便抛出
-
运行时异常
在程序执行期间发生的异常,称为运行时异常,也称为非受检查异常(Unchecked Exception)
常见的有:NullPointerException、ArrayIndexOutOfBoundsException、ArithmeticException
注:
编译时出现的语法性错误,不能称之为异常。例如例如将System.out.println拼写错了,写成了system.out.println,此时编译过程中就会出错,这是"编译期"出错。而运行时异常指的是程序已经通过编译得到了.class文件,再由JVM执行过程中出现的错误
2.异常的处理
2.1 异常的抛出
在Java中,可以借助throw
关键字,抛出一个指定的异常对象,将错误信息告知给调用者
throw new XXXException(“异常产生的原因”);
代码示例:
package demo3;
public class Test1 {
public static int getElement(int[] array,int index) {
if (array == null) {
throw new NullPointerException("传递的数组为null");
}
if (index < 0 || index >= array.length) {
throw new ArrayIndexOutOfBoundsException("传递的数组下标越界");
}
return array[index];
}
public static void main(String[] args) {
int[] arr = {1,2,3};
getElement(arr,100);
}
}
//执行结果
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 传递的数组下标越界
注:
- throw必须写在方法体内部
- 抛出的对象必须是Exception或者Exception的子类对象
- 如果抛出的是RunTimeException或者RunTimeException的子类,则可以不用处理,直接交给JVM来进行处理
- 如果抛出的是编译时异常,用户必须处理,否则无法通过编译
- 异常一旦抛出,其后面的代码就不会执行
2.2 异常的捕获
异常的捕获,也就是异常的具体处理方式,主要有两种:异常声明throws
以及 try-catch捕获处理
2.2.1 异常声明throws
throws一般放在方法声明时参数列表之后,当方法中抛出编译时异常,用户必须处理该异常,此时就可以借助throws将异常抛给方法的调用者来处理。即当前方法不处理异常,提醒方法的调用者处理异常
修饰符 返回值类型 方法名(参数列表) throws 异常类型1,异常类型2... {
//...
}
代码示例:
package demo2;
public class Person {
private String name;
private String gender;
int age;
public Person clone() throws CloneNotSupportedException { //提醒调用者处理该异常
return (Person) super.clone();
}
public static void main(String[] args) {
Person p = new Person();
}
}
//程序正常执行不会报错
注:
- throws 必须跟在方法的参数列表之后
- 声明的异常必须是Exception或者Exception的子类
- 方法内部如果抛出了多个异常,thorws之后必须跟多个异常类型,直接用逗号隔开,如果抛出多个异常类型具有父子关系,直接声明父类即可
- 调用声明抛出异常的方法是,调用者必须对该异常进行处理,或者继续使用throws抛出
2.2.2 try-catch捕获处理
throws并没有对异常进行真正处理,而是将异常报告抛出给了异常方法的调用者,由调用者处理。如果真正要对异常进行处理,可以使用try-catch
语法格式:
try {
//将可能出现异常的代码放在这里
}catch(要捕获的异常类型 e) {
//如果try中的代码抛出异常了,此处catch捕获时异常类型与try中抛出的异常类型一致时,或者是try中抛出异常的基类时,就会被捕获到
//对异常就可以正常处理,处理完成后,跳出try-catch结构,继续执行后续代码
}finally {
//此处代码一定会被执行到
}
代码示例:
package demo3;
public class Test2 {
public static void main(String[] args) {
try {
int[] arr = {1, 2, 3};
System.out.println(arr[100]);
System.out.println("aa"); // 这里不会被执行
}catch (ArrayIndexOutOfBoundsException e) {
System.out.println("此处数组越界异常");
}
finally {
System.out.println("Hello!!"); // 这里一定会被执行
}
}
}
//执行结果
此处数组越界异常
Hello!!
注:
-
try内抛出异常位置之后的代码不会被执行
-
如果抛出异常类型与catch中的异常类型不匹配,即异常不会被成功捕获,也就不会被处理,继续往外抛,直到JVM收到后中断程序(异常是按照类来捕获的)
package demo3; public class Test2 { public static void main(String[] args) { try { int[] arr = {1, 2, 3}; System.out.println(arr[100]); //数组越界 System.out.println("aa"); // 这里不会被执行 }catch (NullPointerException e) { //此处数组越界异常被改为空指针异常,使得catch无法捕获到try中的异常 System.out.println("此处数组越界异常"); } finally { System.out.println("Hello!!"); // 这里一定会被执行 } } } Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 100
-
try中可能会抛出多个不同的异常对象,则必须用多个catch来捕获
package demo3; public class Test2 { public static void main(String[] args) { try { int[] arr = {1, 2, 3}; int[] arr1 = null; System.out.println(arr[100]); System.out.println(arr1[0]); //这里不会被执行 System.out.println("aa"); // 这里不会被执行 }catch (ArrayIndexOutOfBoundsException e) { System.out.println("此处数组越界异常"); }catch (NullPointerException e) { System.out.println("此处空指针异常"); } finally { System.out.println("Hello!!"); // 这里一定会被执行 } } } //执行结果 此处数组越界异常 Hello!!
如果多个异常的处理方式完全相同,也可以写成这样:
catch (ArrayIndexOutOfBoundsException|NullPointerException e) { //... }
如果异常之间具有父子关系,一定是子类异常在前catch,父类异常在后catch,否则语法错误:
package demo3; public class Test2 { public static void main(String[] args) { try { // int[] arr = {1, 2, 3}; int[] arr1 = null; // System.out.println(arr[100]); System.out.println(arr1[0]); System.out.println("aa"); }catch (Exception e) { //Exception 可以捕获到所有异常 System.out.println("此处数组越界异常"); }catch (NullPointerException e) { System.out.println("此处空指针异常"); } finally { System.out.println("Hello!!"); // 这里一定会被执行 } } } //执行结果 java: 已捕获到异常错误java.lang.NullPointerException
注:由于Exception类是所有异常类的父类,因此可以用这个类型表示捕捉所有异常
2.2.3 finally
在写程序时,有些特定的代码,不论程序是否发生异常,都需要执行,比如程序中打开的资源:网络连接、数据库连接、IO流等,在程序正常或者异常退出时,必须要对资源进行回收。另外,因为异常会引发程序的跳转,可能导致有写语句执行不到,finally
就是用来解决这个问题的。
语法格式:
try{
//可能会发生异常的代码
}catch{
//对捕获到的异常进行处理
}finally {
//此处的语句无论是否发生异常,都会被执行到
}
//如果没有抛出异常,或者异常被捕获处理了,这里的代码也会执行
代码示例:
package demo4;
public class Test1 {
public static void main(String[] args) {
try {
int[] arr = {1,2,3};
System.out.println(arr[100]);
}catch (ArrayIndexOutOfBoundsException e) {
System.out.println("数组越界溢出");
}finally {
System.out.println("这里一定会被执行Hello!!");
}
System.out.println("Hello World");
}
}
//执行结果
数组越界溢出
这里一定会被执行Hello!!
Hello World
2.3 异常的处理流程
关于“调用栈”
方法之间是存在相互调用关系的,这种调用关系我们可以用“调用栈”来描述,在JVM中有一块内存空间称为“虚拟机栈”,专门用来存储方法之间的调用关系。当代码中出现异常的时候,我们就可以使用
e.printStackTrace()
;的方式查看出现异常代码的调用栈
如果本方法中没有合适的处理异常的方式,就会沿着调用栈向上传递
代码示例:
package demo4;
public class Test2 {
public static void main(String[] args) {
try {
func();
}catch(ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
}
System.out.println("after try catch");
}
public static void func() {
int[] arr = {1,2,3};
System.out.println(arr[100]);
}
}
//执行结果
java.lang.ArrayIndexOutOfBoundsException: 100
at demo4.Test2.func(Test2.java:15)
at demo4.Test2.main(Test2.java:6)
after try catch
如果向上一直传递都没有合适的方法处理异常,最终就会交给JVM处理,程序就会异常终止(和我们最开始未使用try-catch时是一样的)
package demo4;
public class Test2 {
public static void main(String[] args) {
func();
System.out.println("after try catch");
}
public static void func() {
int[] arr = {1,2,3};
System.out.println(arr[100]);
}
}
//执行结果
java.lang.ArrayIndexOutOfBoundsException: 100
at demo4.Test2.func(Test2.java:15)
at demo4.Test2.main(Test2.java:6)
after try catch
可以看到,程序已经异常终止了,没有执行到 System.out.println("after try catch");
这一行
【异常处理流程总结】
- 程序先执行try中的代码
- 如果try中的代码出现异常,就会结束try中的代码,看和catch中的异常是否匹配
- 如果找到匹配的异常类型,就会执行catch中的代码
- 如果没有找到匹配的异常类型,就会将异常向上传递到上次调用者
- 无论是否找到匹配的异常类型,finally中的代码都会被执行到(在该方法结束之前执行)
- 如果上层调用者也没有处理的了异常,就继续向上传递
- 一直到main方法也没有合适的代码处理异常,就会交给JVM来进行处理,此时程序就会异常终止
3.自定义异常类
Java中虽然已经内置了丰富的异常类,但是并不能完全表示实际开发中所遇到的一些异常,此时就需要维护符合我们实际情况的异常结构
例如,我们实现一个用户登录功能:
package demo5;
public class Login {
private String userName = "XiaoMa";
private String passWord = "11111";
public static void loginInfo(String userName, String passWord) {
if (!userName.equals(userName)) {
}
if (!passWord.equals(passWord)) {
}
System.out.println("登陆成功!!");
}
public static void main(String[] args) {
loginInfo("XiaoMa","11111");
}
}
此时我们在处理用户名密码错误的时候可能就需要抛出两种异常,我们可以基于已有的异常类进行扩展(继承),创建和我们业务相关的异常类
具体方式:
- 自定义异常类,然后继承Exception或者RunTimeException
- 实现一个带有String类型参数的构造方法,参数含义:出现异常的原因
class UserNameException extends Exception {
public UserNameException(String message) {
super(message);
}
}
class PassWordException extends Exception {
public PassWordException(String message) {
super(message);
}
}
此时我们的login代码可以改成:
package demo5;
public class Login {
public static String userName = "XiaoMa";
private static String passWord = "11111";
public static void loginInfo(String username, String password) throws UserNameException, PassWordException {
if (!userName.equals(username)) {
throw new UserNameException("用户名错误");
}
if (!passWord.equals(password)) {
throw new PassWordException("密码错误");
}
System.out.println("登陆成功!!");
}
public static void main(String[] args) throws UserNameException, PassWordException {
try {
loginInfo("Xiao","11111");
}catch (UserNameException e) {
e.printStackTrace();
}catch (PassWordException e) {
e.printStackTrace();
}
}
}
class UserNameException extends Exception {
public UserNameException(String message) {
super(message);
}
}
class PassWordException extends Exception {
public PassWordException(String message) {
super(message);
}
}
//执行结果
demo5.UserNameException: 用户名错误
at demo5.Login.loginInfo(Login.java:10)
at demo5.Login.main(Login.java:23)
注:
- 自定义异常通常会继承自Exception||RuntimeException
- 继承自Exception的异常默认是受查异常
- 继承自RunTimeException的异常默认是非受查异常