Java异常
Java异常体系
顶层类Throwable派生出了Error(错误)和Exception(异常)两个子类:
Error:Java运行时内部错误或资源耗尽错误,应用程序不抛出此类异常,这种内部类错误一旦出现,只能告知用户并是程序终止。如,内存溢出等。
Exception:是各种异常类的父类。有一个子类RunTimeException,又派生出很多常见的异常类,如,NullPointerException,IndexOutOfBoundsException等。
派生于Error类或者RuntimeException类的所有异常称为非受查异常,所有其他的异常称为受查异常。若代码抛出受查异常,则必须要显示地处理。
异常即指程序在运行时出现错误时通知调用者的一种机制。
常见异常
除数是0
System.out.println(10/0);
//执行结果
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Blog.main(Blog.java:9)
数组越界
int[] array = {1,2,3};
System.out.println(array[3]);
//执行结果
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 3
at Blog.main(Blog.java:11)
访问空对象
public class Blog {
public int num = 10;
public static void main(String[] args) {
Blog blog = null;
System.out.println(blog.num);
}
}
//执行结果
Exception in thread "main" java.lang.NullPointerException
at Blog.main(Blog.java:14)
异常的基本用法
捕获异常
基本语法:
try{
//有可能出现异常的语句;
}catch(异常类型 异常对象){
//处理异常
}finally{
//异常的出口
}
- try代码块中放的是可能会出现异常的代码;
- catch代码块中放的是出现异常后的处理行为;
- finally代码块中的代码用于处理善后工作,会在最后执行(一定会被执行);
- catch和finally代码块可根据实际情况选择加或不加。
异常处理流程 - 程序先执行try中的代码;
- 若try中的代码出现异常,就会结束try中的代码,并检查和catch中的异常类型是否匹配;
- 若匹配,则执行catch中的代码;
- 若无匹配的异常类型,则会将异常向上传递到上层调用者;
- 若上层调用者也没有相匹配的异常类型,则继续向上传递;
- 一直到main方法也没有合适的代码来处理异常,则会交给JVM来处理,即程序会异常终止;
- 无论是否找到了相匹配的类型,finally中的代码都会被执行(在该方法结束之前执行)。
示例1 不处理异常
System.out.println("before");
System.out.println(10/0);
System.out.println("last");
//执行结果
before
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Blog.main(Blog.java:17)
在异常之前的语句可正常运行,但一遇见异常,因为没有对应的异常处理,所以JVM来处理,即程序运行终止。
示例2 使用try catch后的程序
try{
System.out.println("before");
System.out.println(10/0);
System.out.println("after");
}catch(ArithmeticException e){
e.printStackTrace();
}
System.out.println("have catch");
System.out.println("after");
//执行结果
before
java.lang.ArithmeticException: / by zero
at Blog.main(Blog.java:22)
have catch
由上述代码可知,若在try代码块中捕获到异常后,则异常语句后面的代码将不会再被执行。之后,再根据catch中的语句进行处理。try catch语句后的代码也会继续被执行。
示例3 catch只能处理对应种类的异常
try{
System.out.println("before");
System.out.println(10/0);
System.out.println("after");
}catch(ArrayIndexOutOfBoundsException e){
e.printStackTrace();
}
System.out.println("have catch");
//执行结果
before
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Blog.main(Blog.java:31)
此时,catch语句不能捕获到对应的算术运算异常,因为异常类型不匹配。所以交给JVM运行,程序运行终止。
示例4 catch可以有多个
try{
System.out.println("before");
System.out.println(10/0);
System.out.println("after");
}catch(ArrayIndexOutOfBoundsException e){
System.out.println("数组下标越界异常");
e.printStackTrace();
}catch(ArithmeticException e){
System.out.println("算术运算异常");
e.printStackTrace();
}
System.out.println("have catch");
//执行结果
before
算术运算异常
java.lang.ArithmeticException: / by zero
at Blog.main(Blog.java:39)
have catch
一段代码可能会抛出多种不同类型的异常,不同的异常有不同的处理方式,因此可以搭配多个catch代码块。
若多个异常的处理方式完全相同,也可写成下述代码
try{
System.out.println("before");
System.out.println(10/0);
System.out.println("after");
}catch(ArrayIndexOutOfBoundsException | ArithmeticException e){
System.out.println("异常");
e.printStackTrace();
}
System.out.println("have catch");
示例5 finally进行完善工作
public static int fac(){
//finally善后
//1、捕获异常
try{
System.out.println(10/0);
}catch(ArrayIndexOutOfBoundsException e){
e.printStackTrace();
return 2;
}catch(ArithmeticException e){
e.printStackTrace();
return 3;
}finally{
return 4;
}
//执行结果
java.lang.ArithmeticException: / by zero
at Blog.fac(Blog.java:65)
at Blog.main(Blog.java:60)
4
//未捕获异常
try{
System.out.println(10/0);
}catch(ArrayIndexOutOfBoundsException e){
e.printStackTrace();
return 2;
}finally{
return 4;
}
}
//执行结果
4
上述代码均返回的是4,即finally代码块中的内容无论异常是否被捕获到,最终都会被执行到。
由此,可以将Scanner.close()放到代码块中,保证其一定会被执行到。代码如下所示
Scanner scan = new Scanner(System.in);
try{
int num = scan.nextInt();
System.out.println(10/0);
}catch(ArrayIndexOutOfBoundsException | ArithmeticException e){
e.printStackTrace();
}finally{
System.out.println("hello");
scan.close();
}
示例6 使用try回收资源
上述代码的一种等价写法。
try(Scanner scan = new Scanner(System.in)){
int num = scan.nextInt();
System.out.println(10/0);
}catch(ArrayIndexOutOfBoundsException | ArithmeticException e){
e.printStackTrace();
}
将Scanner在try的代码块中创建,以保证try执行完毕后自动调用Scanner的close()方法。
示例7 若本方法中没有合适的处理异常的方式,则会沿着调用栈向上传递。
public static void func() {
int[] arr = {1,2,3};
System.out.println(arr[10]);
}
public static void main(String[] args) {
try{
func();
}catch(ArrayIndexOutOfBoundsException e){
System.out.println("下标越界");
e.printStackTrace();
}
System.out.println("after");
}
//执行结果
下标越界
java.lang.ArrayIndexOutOfBoundsException: 10
at Blog.func(Blog.java:11)
at Blog.main(Blog.java:15)
after
若一直向上传递都没有合适的异常处理方法,则会交给JVM处理,程序就会异常终止。
抛出异常
除了Java内置的类会抛出一些异常之外,也可以使用throw关键字来自行抛出异常。
public static void main(String[] args) {
divide(10,0);
}
public static int divide(int x, int y){
if(y == 0){
throw new ArithmeticException("除数为0");
}
return x/y;
}
//执行结果
Exception in thread "main" java.lang.ArithmeticException: 除数为0
at Blog.divide(Blog.java:14)
at Blog.main(Blog.java:10)
自定义异常
场景 用户登录功能
若账号输入错误,则返回“Wrong Username”信息,并终止;若密码错误,则返回“Wrong Password”信息,并终止;否则,显示“登陆成功”。
class UserError extends Exception{
public UserError(String message){
super(message);
}
}
class PassError extends Exception{
public PassError(String message){
super(message);
}
}
public class MyException {
private static String username = "admin";
private static String password = "12345";
public static void main(String[] args) {
try{
login("admin", "1234");
}catch(UserError userError){
userError.printStackTrace();
}catch(PassError passError){
passError.printStackTrace();
}
}
public static void login(String username, String password) throws UserError, PassError{
if(!MyException.username.equals(username)){
throw new UserError("Wrong Username!");
}
if(!MyException.password.equals(password)){
throw new PassError("Wrong Password");
}
System.out.println("Success!");
}
}
在上述场景中,定义了两个异常类:UserError和PassError。他们都继承自Exception这个异常类。在使用两个异常类时,需在方法名称后面对其进行声明,即:throws UserError,PassError。