1.异常初识
- 运行时异常(非受查异常)
算数异常,数组越界异常,空指针异常。都是在程序运行的过程中发生的异常- 编译时异常(受查异常)
1.处以0
System.out.println(1/0)
Exception in thread "main" java.lang.ArithmeticException: / by zero
2.数组下标越界
int[] arr = new int[10];
System.out.println(arr[10]);
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 10
3. 访问 null 对象
private static int num = 10;
public static void main(String[] args) {
test t;
t=null;
System.out.println(t.num);
}
Exception in thread "main" java.lang.NullPointerException
4. 异常的好处
1.LBYL【Look Before Your Leap:在操作之前就做充分的检查】
boolean ret = false;
ret = 登陆游戏();
if (!ret) {
处理登陆游戏错误;
return;
}
ret = 开始匹配();
if (!ret) {
处理匹配错误;
return;
}
ret = 游戏确认();
if (!ret) {
处理游戏确认错误;
return;
}
ret = 选择英雄();
if (!ret) {
处理选择英雄错误;
return;
}
ret = 载入游戏画面();
if (!ret) {
处理载入游戏错误;
return;
}
......
EAFP【It’s Easier to Ask Forgiveness than Permission. “事后获取原谅比事前获取许可更容易”. 也就是先操作, 遇到 问题再处理】
try {
登陆游戏();
开始匹配();
游戏确认();
选择英雄();
载入游戏画面();
...
} catch (登陆游戏异常) {
处理登陆游戏异常;
} catch (开始匹配异常) {
处理开始匹配异常;
} catch (游戏确认异常) {
处理游戏确认异常;
} catch (选择英雄异常) {
处理选择英雄异常;
} catch (载入游戏画面异常) {
处理载入游戏画面异常;
} ......
对比两种不同风格的代码, 我们可以发现
使用第一种方式, 正常流程和错误处理流程代码混在一起, 代码整体显的比较 混乱.
第二种方式正常流程和错误流程是分离开的, 更容易理解代码.
2. 异常的基本用法
try{
有可能出现异常的语句;
}catch(异常类型 异常对象){
处理
}finally{
处理
}
- try 代码块中放的是可能出现异常的代码.
- catch 代码块中放的是出现异常后的处理行为.
- finally 代码块中的代码用于处理善后工作, 会在最后执行. 其中 catch 和 finally 都可以根据情况选择加或者不加
1.不处理异常
int[] arr = {
1, 2, 3};
System.out.println("before");
System.out.println(arr[100]);
System.out.println("after");
before
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 100
我们发现一旦出现异常, 程序就终止了. after 没有正确输出
2.使用 try catch后的程序执行过程
public static void main(String[] args) {
int[] arr = {
1, 2, 3};
try{
System.out.println("before");
System.out.println(arr[100]);
System.out.println("after");
}catch (ArrayIndexOutOfBoundsException e){
e.printStackTrace();// 打印异常程序调用栈
}
System.out.println("after try catch");
}
before
after try catch
java.lang.ArrayIndexOutOfBoundsException: 100
at test.main(test.java:8)
我们发现, 一旦 try 中出现异常, 那么 try 代码块中的程序就不会继续执行, 而是交给 catch 中的代码来执行. catch 执 行完毕会继续往下执行.
关于异常的处理方式
异常的种类有很多, 我们要根据不同的业务场景来决定
- 对于比较严重的问题(例如和算钱相关的场景), 应该让程序直接崩溃, 防止造成更严重的后果
- 对于不太严重的问题(大多数场景), 可以记录错误日志, 并通过监控报警程序及时通知程序猿
对于可能会恢复的问题(和网络相关的场景), 可以尝试进行重试.
关于调用栈
方法之间是存在相互调用关系的, 这种调用关系我们可以用 “调用栈” 来描述. 在 JVM 中有一块内存空间称为 “虚 拟机栈” 专门存储方法之间的调用关系. 当代码中出现异常的时候, 我们就可以使用 e.printStackTrace(); 的 方式查看出现异常代码的调用栈.
3. catch 只能处理对应种类的异常
public static void main(String[] args) {
int[] arr = {
1, 2, 3};
try{
System.out.println("before");
System.out.println(arr[100]);
System.out.println("after");
}catch (NullPointerException e){
e.printStackTrace();// 打印异常程序调用栈
}
System.out.println("after try catch");
}
before
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 100
at test.main(test.java:8)
此处的 catch 只能捕获 空指针 异常而不能捕获 数组越界 异常,因为异常类型不匹配
4. catch 可以有多个
public static void main(String[] args) {
int[] arr = {
1, 2, 3};
try{
System.out.println("before");
System.out.println(arr[100]);
System.out.println("after");
}catch (NullPointerException e){
e.printStackTrace();// 打印异常程序调用栈
}catch (ArrayIndexOutOfBoundsException e){
e.printStackTrace();
}
System.out.println("after try catch");
}
before
after try catch
java.lang.ArrayIndexOutOfBoundsException: 100
at bit.test.main(test.java:8)
一段代码可能会抛出多种不同的异常, 不同的异常有不同的处理方式. 因此可以搭配多个 catch 代码块. 如果多个异常的处理方式是完全相同, 也可以写成这样:
public static void main(String[] args) {
int[] arr = {
1, 2, 3};
try{
System.out.println("before");
System.out.println(arr[100]);
System.out.println("after");
}catch (NullPointerException | ArrayIndexOutOfBoundsException e){
e.printStackTrace();// 打印异常程序调用栈
}
System.out.println("after try catch");
}
before
after try catch
java.lang.ArrayIndexOutOfBoundsException: 100
5. 也可以用一个 catch 捕获所有异常(不推荐)
public static void main(String[] args) {
int[] arr = {
1, 2, 3};
try{
System.out.println("before");
System.out.println(arr[100]);
System.out.println("after");
}catch (Exception e){
e.printStackTrace();// 打印异常程序调用栈
}
System.out.println("after try catch");
}
before
after try catch
java.lang.ArrayIndexOutOfBoundsException: 100
异常范围太广,不容易定位问题
由于 Exception 类是所有异常类的父类. 因此可以用这个类型表示捕捉所有异常
6. finally 表示最后的善后工作, 例如释放资源
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
try{
int number = scanner.nextInt();
}catch (Exception e){
e.printStackTrace();// 打印异常程序调用栈
}finally {
scanner.close();
System.out.println("scanner closed");
}
}
1
scanner closed
finally 的执行不需要条件,无论有无异常都会执行;catch 只能发生相应的异常才能执行
7. 使用 try 负责回收资源
刚才的代码可以有一种等价写法, 将 Scanner 对象在 try 的 ( ) 中创建, 就能保证在 try 执行完毕后自动调用 Scanner 的 close 方法.
public static void main(String[] args) {
try(Scanner scanner = new Scanner(System.in)){
int number = scanner.nextInt();
}catch (Exception e){
e.printStackTrace();// 打印异常程序调用栈
}
}
8. 如果本方法中没有合适的方法调用栈就会沿着调用栈向上传递
private static void func(){
int[] arr = {
1,2,3};
System.out.println(arr[100]);
}
public static void main(String[] args) {
try{
func();
}catch (ArrayIndexOutOfBoundsException e){
e.printStackTrace();
}
System.out.println("after catch");
}
java.lang.ArrayIndexOutOfBoundsException: 100
test.func(test.java:6)
test.main(test.java:10)
after catch
9. 异常处理流程
- 首先执行
try
之中的代码 - 如果
try
中的代码出现异常, 就结束try
中的代码, 看和catch
是否匹配 - 如果找到
catch
匹配的异常类型, 就执行catch
之中的代码 - 如果没有找到
catch
之中的代码就会将异常交给上层调用者来处理 - 无论是否匹配到这个异常类型, 都会执行在该方法结束之前执行
finally
的方法 - 如果上层调用者也没有这个异常的处理方法就会继续向上传递
- 一直到
main
函数也没有合适的代码处理异常, 就会交给JVM
来处理, 此时程序就会终止
10. 抛出异常【throw】
public static int divide(int x, int y) {
if (y == 0) {
throw new ArithmeticException("0 作为被除数");
}
return x / y;
}
public static void main(String[] args) {
System.out.println(divide(10,0));
System.out.println(divide(10,5));
}
Exception in thread "main" java.lang.ArithmeticException: 0 作为被除数
抛出自己的异常类
class MyException extends RuntimeException{
public MyException() {
}
public MyException(String message) {
super(message);
}
}
public class test {
private static void func(){
int a=10;
if (a==10){
throw new MyException();
}
}
public static void main(String[] args) {
func();
}
}
Exception in thread "main" bit.MyException
由于继承的
RuntimeException
,所以是运行时异常,也叫非受查异常
class MyException extends Exception{
public MyException() {
}
public MyException(String message) {
super(message);
}
}
public class test {
private static void func() throws MyException {
int a=10;
if (a==10){
throw new MyException("异常捕获的信息");
}
}
public static void main(String[] args) throws MyException {
func();
}
}
由于继承的是
Exception
, 所以是编译时异常,受查异常
受查异常 | 非受查异常 |
---|---|
Exception | RunTimeException |
11. 异常说明
我们在处理异常的时候,通常希望看到可能抛出什么异常。可以使用
throws
把可能抛出的异常显示的标注在方法定义的位置,来提醒调用者要捕获这些异常
public static int divide(int x, int y) throws ArithmeticException{
if (y == 0) {
throw new ArithmeticException("0 作为被除数");
}
return x / y;
}
public static void main(String[] args) {
System.out.println(divide(10,0));
System.out.println(divide(10,5));
}
Exception in thread "main" java.lang.ArithmeticException: 0 作为被除数
12. 关于 finally 的注意事项
finally 中的代码一定会执行到,这或许会带来一些误解和麻烦
private static int func(){
try{
return 0;
}finally {
return 1;
}
}
public static void main(String[] args) {
System.out.println(func());
}
1
finally 执行的时机是在 方法返回 之前【如果 try 或者 catch 中有 return ,会在这个 return 之前执行 finally】,如果 finally 也有 return 则执行 finally 中的 return 而不会执行 try 或 catch 中的 return
3. Java异常体系
- 顶层
throwable
派生出两个重要自类,Error
和Exception
- 其中
Error
是指 Java 内部错和资源耗尽错误,应用程序不抛出此类异常,这种错误很少见,一但出现这种错误除了告知用户并使程序终止以外别无它力 Exception
是所有异常的父类Exception
有一个子类RuntimeException
衍生出许多的我们常见的异常类
- Java 语法规范将派生于
Error
和RunTimeException
类的所有异常称为 非受查异常,所有的其它异常类成为 受查异常- 如果一个类继承了受查异常, 则必须对它显示进行处理
由于 func 抛出了 Exception 这个受查异常,所以在提醒方法调用者可能会把抛出异常,需要手动进行处理,因此需要在调用它的地方 main 函数中进行手动添加throws Exception处理,否则就会继续上交给 JVM 进行处理
处理方法分为两种
1. 使用 try catch 包裹起来
private static void func() throws Exception{
int[] arr = {
1,2,3};
System.out.println(arr[100]);
}
public static void main(String[] args) {
try {
func();
} catch (Exception e) {
e.printStackTrace();
}
}
2. 在方法上加上异常说明, 相当于将处理动作交给上级调用者
private static void func() throws Exception{
int[] arr = {
1,2,3};
System.out.println(arr[100]);
}
public static void main(String[] args) throws Exception {
func();
}
option+enter 进行快速修补代码
4. 自定义异常类
Java 虽然内置了丰富的异常类,但是我们实际应用中会对异常累进行一些扩展。创建符合我们的异常类。
throw 的非受查异常没有参数
class MyException extends RuntimeException{
public MyException() {
super();
}
public MyException(String message) {
super(message);
}
}
public class test {
private static void func(){
int a=10;
if (a==10){
throw new MyException();
}
}
public static void main(String[] args) {
func();
}
}
Exception in thread "main" MyException
throw 的非受查异常异常有参数
class MyException extends RuntimeException{
public MyException() {
super();
}
public MyException(String message) {
super(message);
}
}
public class test {
private static void func(){
int a=10;
if (a==10){
throw new MyException("haha");
}
}
public static void main(String[] args) {
func();
}
}
Exception in thread "main" MyException: haha
throw 的受查异常使用 throws 层层提醒调用者
class MyException extends Exception{
public MyException() {
super();
}
public MyException(String message) {
super(message);
}
}
public class test {
private static void func() throws MyException {
int a=10;
if (a==10){
throw new MyException("haha");
}
}
public static void main(String[] args) throws MyException {
func();
}
}
Exception in thread "main" MyException: haha
throw 的受查异常使用 try catch 直接捕获完毕不用提交给调用者
class MyException extends Exception{
public MyException() {
super();
}
public MyException(String message) {
super(message);
}
}
public class test {
private static void func(){
int a=10;
if (a==10){
try {
throw new MyException("haha");
} catch (MyException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
func();
}
}
MyException: haha