基本介绍
背景
- 在介绍异常之前,我们先来看看以下几种常见的异常
除以0
System.out.println(15/0);
//Exception in thread "main" java.lang.ArithmeticException: / by zero
数组下标越界
int[] arr = {1,2,3};
System.out.println(arr[12])
//Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 12
访问空对象
String n = null;
System.out.println(n.length());
//Exception in thread "main" java.lang.NullPointerException
- 防御式编程
错误在代码中是客观存在的,我们需要在出现问题时能够通过某种手段来提醒程序员。这里有两种主要的方式:- LBYL(Look Before You Leap),在操作之前作充分的准备
- EAFP: It’s Easier to Ask Forgiveness than Permission. 事后获取原谅比事前获取许可更容易". 也就是先操作, 遇到问题再处理,EAFP即为我们要讨论的异常的核心思想
LBYL风格式代码
int x = 1;
if(x < 0){
//提醒x小于0;
}else if(x == 0){
//提醒x等于0
}
}
概念
java语言中,将程序执行中发生的不正常情况称为异常,(语法错误和拼写错误不是异常,在java中这些错误被称为Error)
异常分类
异常事件分类
Error(错误),Java虚拟机无法解决的严重问题,如:jvm系统内部错误,资源耗尽等严重情况。比如:StackOverflowError[栈溢出]和OOM(out of memory),Error是严重错误,程序会崩溃.。
Exception:其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如空指针访问,试图读取不存在的文件,网络连接中断等等,Exception可分为两大类:运行时异常[程序运行时发生的异常]和编译时异常[编程时,编译器检查出的异常]
Throwable
Error
栈溢出错误、堆溢出错误等等
异常
编译异常
IOException
ClassNotFoundException
CloneNotFoundException
运行异常
数组越界、空指针异常、计算异常等等
异常处理
try-catch-finally
- 基本语法:
try{
有可能出现异常的语句 ;
}[catch (异常类型 异常对象) {
} … ]
[finally {
异常的出口
}]
try代码块中存放可能出现异常的代码
catch代码块中存放的是对出现的异常处理的代码
finally中的代码块用于处理善后工作,会在最后执行
- 异常捕获细节
- try代码块中如果异常发生了,则不会执行try代码块中异常语句之后的代码,直接进入catch代码块
int[] arr = {1,2,3};
try {
int n = arr[4];
System.out.println(“这里的代码不会执行”);
} catch (ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
} finally {
//释放资源
}
以上代码存在数组越界异常,无法执行第四行的输出代码- 如果异常没有发生,则顺序执行try的代码块,不会进入到catch
int[] arr = {1,2,3};
try {
int n = arr[2];
System.out.println(“这里的代码会执行”);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println(“这里的代码会执行”);
} finally {
//释放资源
}
以上代码没有异常,try中的代码会全部执行,catch中的代码不会执行- finally代码块中的语句一定会执行,所以finally代码块中通常执行某些资源的释放和关闭,尽量不要在finally代码块中方return语句
int[] arr = {1,2,3};
try {
int n = arr[4];
System.out.println(“这里的代码会执行”);
} catch (ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
return 10;
} finally {
//释放资源
return 20
}
以上代码发生了异常会执行catch代码块中的return 10语句,但finally是最后一定执行的,所以最终会返回20- 异常处理流程
- 程序先执行 try 中的代码
- 如果 try 中的代码出现异常, 就会结束 try 中的代码, 看和 catch 中的异常类型是否匹配.
- 如果找到匹配的异常类型, 就会执行 catch 中的代码
- 如果没有找到匹配的异常类型, 就会将异常向上传递到上层调用者.
- 无论是否找到匹配的异常类型, finally 中的代码都会被执行到(在该方法结束之前执行).
- 如果上层调用者也没有处理的了异常, 就继续向上传递.
- 一直到 main 方法也没有合适的代码处理异常, 就会交给 JVM 来进行处理, 此时程序就会异常终止
throw
除了 Java 内置的类会抛出一些异常之外, 也可以手动抛出某个异常. 使用 throw 关键字完成这个操作
public static void main(String[] args) {
System.out.println(divide(10, 0));
}
public static int divide(int x, int y) {
if (y == 0) {
throw new ArithmeticException("抛出除 0 异常");
//可以认为设定异常输出信息
}
return x / y;
}
在处理异常时,为了显示的知道异常发生的位置,我们可以用throws将发生的异常类型在方法声明中显示`
public static int divide(int x, int y) throws ArithmeticException {
if (y == 0) {
throw new ArithmeticException("抛出除 0 异常");
}
return x / y;
}
自定义异常
在实际的业务开发中,我们可能需要对异常类进行拓展,创建满足我们实际情况中会发生的异常
- 在自定义异常时,我们通常将异常类型定义RuntimeException而不定义为编译异常。
int x = -1;
if(x<=0){
throw new RuntimeException("数字不是正数");
//新定义的异常中的描述信息可以随意设定
}
System.out.println("数字为正数");
}
- 我们也可以自定义一个异常类`
public class TestDemo {
private static final String userName = "admin";
public static void main(String[] args) {
login("Admin");
}
public static void login(String name){
if(!name.equals(userName)){
throw new UserNameException("用户名错误!");
}
}
}
class UserNameException extends RuntimeException{
public UserNameException(String message) {
super(message);
}
}
//最终运行结果会提示用户名错误!