JAVA SE—— 异常

但使书种多,会有岁稔时。这篇文章主要讲述JAVA SE—— 异常相关的知识,希望能为你提供帮助。
@TOC
一、认识异常 什么是异常异常是程序运行过程中出现的一种错误。异常的种类有很多种,分别代表不同的含义,一旦出现某个异常,就会明确的告诉程序猿出现异常的原因,所以是帮助我们解决问题的一种很好的手段。
我们常见的异常有:
算术异常:ArithmeticException

System.out.println(10 / 0); Exception in thread "main" java.lang.ArithmeticException: / by zero

数组下标越界:超出索引范围:ArrayIndexOutOfBoundsException
int[] arr = 1, 2, 3; System.out.println(arr[100]); // 执行结果 Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 100

对空指针访问:NullPointerException
public class Test public int num = 10; public static void main(String[] args) Test t = null; System.out.println(t.num); // 执行结果 Exception in thread "main" java.lang.NullPointerException

除了上面这几种最常见的异常,还有很多,此处不再举例。
异常分为编译时异常(受查异常)和运行时异常(非受查异常)。
编译时异常就是编译时期程序会报错,此处需要程序员自己去更改代码程序,例如将 System.out.println 拼写错了, 写成了 system.out.println. 此时编译过程中就会出
错, 这是 “编译期” 出错。
运行时异常是运行时报错,运行时已经编译,产生.class文件交给JVM处理。因此是在JVM处报错的。
异常的种类有很多, 不同种类的异常具有不同的含义, 也有不同的处理方式。
二、防御式编程(引入异常的原因)以王者为例:
①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; ......xxxxxxxxxx boolean ret = false; ret = 登陆游戏(); if (!ret) // 如果登录失败,执行下面的的操作 处理登陆游戏错误; return; ret = 开始匹配(); if (!ret) // 如果匹配失败,执行下面的的操作 处理匹配错误; return; ret = 游戏确认(); // 如果游戏确认失败,执行下面的的操作if (!ret)处理游戏确认错误; return; ret = 选择英雄(); // 如果选择英雄失败,执行下面的的操作if (!ret)处理选择英雄错误; return; ret = 载入游戏画面(); 如果载入游戏界面失败,执行下面的的操作if (!ret)处理载入游戏错误; return; ......boolean ret=login(); if(!ret)//处理登录失败return; ret=startMatch(); if(!ret)//处理匹配失败return; ret=enterRoom(); if(!ret)//处理进入房间失败return; ret=chooseHero(); if(!ret)//处理选择英雄失败return;

②EAFP(使用异常):Its Easier to Ask Forgiveness than Permission:先斩后奏。
try // 把可能出现异常的代码,放到try当中 登陆游戏(); 开始匹配(); 游戏确认(); 选择英雄(); 载入游戏画面(); ... // catch == 捕捉,捕捉什么呢?捕捉异常 // 异常有很多种,所以不能只捕捉一种异常 catch (登陆游戏异常) 处理登陆游戏异常; catch (开始匹配异常) 处理开始匹配异常; catch (游戏确认异常) 处理游戏确认异常; catch (选择英雄异常) 处理选择英雄异常; catch (载入游戏画面异常) 处理载入游戏画面异常; ......

分析:如果执行到某一步出错,就会抛出异常。一旦抛出异常,就会进入catch这样的代码中执行异常处理逻辑。
上面两种方式中,可以发现第二种更好,它将程序的正常流程和处理错误分离开了,更清楚更容易理解!
三、异常的用法 1.捕捉异常 1.try
try 有可能出现异常的语句 ; [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

2.catch
如果用try catch语句处理异常:
代码2:
JAVA SE—— 异常

文章图片

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 java.lang.ArrayIndexOutOfBoundsException: 100 at demo02.Test.main(Test.java:10) after try catch

这时就把出现异常的栈的位置打印出来了,这就是 e 的一个作用吧
可见,如果此处用try catch语句处理可能会出现异常的语句,即使程序会报错,但是我们处理异常后依然会运行后面的代码。
catch 只能处理对应种类的异常
假设我们修改了代码, 让代码抛出的是空指针异常:
代码3:
int[] arr = 1, 2, 3; try System.out.println("before"); arr = null; System.out.println(arr[100]); System.out.println("after"); catch (ArrayIndexOutOfBoundsException e) e.printStackTrace(); System.out.println("after try catch"); // 执行结果 before Exception in thread "main" java.lang.NullPointerException at demo02.Test.main(Test.java:11)

此时, catch 语句不能捕获到刚才的空指针异常,因为异常类型不匹配。因为没有捕捉到异常,因此后面的语句也不会被执行。
catch捕捉的异常可以有多个。
【JAVA SE—— 异常】代码4:
int[] arr = 1, 2, 3; try System.out.println("before"); arr = null; System.out.println(arr[100]); System.out.println("after"); catch (ArrayIndexOutOfBoundsException e) System.out.println("这是个数组下标越界异常"); e.printStackTrace(); catch (NullPointerException e) System.out.println("这是个空指针异常"); e.printStackTrace(); System.out.println("after try catch"); // 执行结果 before 这是个空指针异常 java.lang.NullPointerException at demo02.Test.main(Test.java:12) after try catch

一段代码可能会抛出多种不同的异常, 不同的异常有不同的处理方式,因此可以搭配多个 catch 代码块,如果多个异常的处理方式是完全相同, 也可以写成这样。两个异常中间用|分开,定义的是同一个变量。
catch (ArrayIndexOutOfBoundsException | NullPointerException e) ...

总结:
3.Exception(所有异常父类)
也可以用一个 catch 捕获Exception中的所有异常(不推荐),Exception异常是所有异常的父类。因为如果漏写了e.printStackTrace(); 就不能确定出现的是哪一种异常,当代码过多时就可以出现不必要的麻烦。
int[] arr = 1, 2, 3; try System.out.println("before"); arr = null; System.out.println(arr[100]); System.out.println("after"); catch (Exception e) e.printStackTrace(); System.out.println("after try catch"); // 执行结果 before java.lang.NullPointerException at demo02.Test.main(Test.java:12) after try catch

JAVA SE—— 异常

文章图片

4.finally
我们再来看一下异常基础语法的学习
try 有可能出现异常的语句 ; [catch (异常类型 异常对象) ... ] [finally 异常的出口 ]

finally作用 :
举例1:
JAVA SE—— 异常

文章图片

运行结果:
JAVA SE—— 异常

文章图片

举例2:
JAVA SE—— 异常

文章图片

举例三:
如果本方法中没有合适的处理异常的方式, 就会沿着调用栈向上传递。即如果在一个方法内部出现了异常,假设没有处理该异常,则交给调用该方法的方法去处理异常。以此类推…如果向上一直传递都没有合适的方法处理异常, 最终就会交给 JVM 处理, 程序就会异常终止(和我们最开始未使用 try catch 时是一样的)
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 demo02.Test.func(Test.java:18) at demo02.Test.main(Test.java:9) after try catch

注意:
finally中不建议写return语句,如果try中有return语句,finally中也有return语句,那么就会执行finally中的return语句,而不会执行try中原有的return语句。
JAVA SE—— 异常

文章图片

2.关于异常的处理方式异常的种类有很多, 我们要根据不同的业务场景来决定.
3.抛出异常除了 Java 内置的类会抛出一些异常之外,使用 throw 关键字完成这个操作,一般来说throw与throws一起使用。注:throw与throws是两个不同的关键字。
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; // 执行结果 Exception in thread "main" java.lang.ArithmeticException: 抛出除 0 异常 at demo02.Test.divide(Test.java:14) at demo02.Test.main(Test.java:9)

在这个代码中, 我们可以根据实际情况来抛出需要的异常. 在构造异常对象同时可以指定一些描述性信息。假设抛出异常后调用该方法的方法没有处理异常,直到main方法仍没有处理异常,则还是交给JVM处理。
4.异常说明我们在处理异常的时候, 通常希望知道这段代码中究竟会出现哪些可能的异常。
我们可以使用 throws 关键字, 把可能抛出的异常显式的标注在方法定义的位置,从而提醒调用者要注意捕获这些异常。
public static int divide(int x, int y) throws ArithmeticException if (y == 0) throw new ArithmeticException("抛出除 0 异常"); return x / y;

四、异常的体系结构
JAVA SE—— 异常

文章图片

顶层类 Throwable 派生出两个重要的子类, ErrorException
?
其中 Error 指的是 Java 运行时内部错误和资源耗尽错误. 应用程序不抛出此类异常. 这种内部错误一旦出现,
除了告知用户并使程序终止之外, 再无能为力. 这种情况很少出现.
?
Exception 是我们程序猿所使用的异常类的父类.
?
其中 Exception 有一个子类称为 RuntimeException , 这里面又派生出很多我们常见的异常类
NullPointerException , IndexOutOfBoundsException 等.
?
Java语言规范将派生于 Error 类或 RuntimeException 类的所有异常称为 非受查异常, 所有的其他异常称为 受查异常.
如果一段代码可能抛出受查异常,必须显式处理
受查异常与非受查异常
JAVA SE—— 异常

文章图片

Java语言规范将派生于 Error 类或 RuntimeException 类的所有异常称为非受查异常, 所有的其他异常称为受查异常。如果一段代码可能抛出 受查异常, 那么必须显式进行处理
public static void main(String[] args) System.out.println(readFile()); public static String readFile() // 尝试打开文件, 并读其中的一行. File file = new File("d:/test.txt"); // 使用文件对象构造 Scanner 对象. Scanner sc = new Scanner(file); return sc.nextLine(); // 编译出错 Error:(13, 22) java: 未报告的异常错误java.io.FileNotFoundException; 必须对其进行捕获或声明以便抛出

如 FileNotFoundException 这样的异常就是受查异常,如果不显式处理, 编译无法通过,显式处理的方式有两种:
1.使用 try catch 包裹起来
因为在输入给sc时可能会出错,因此可以用try catch捕捉,此处虽然运行时可能会报错,但是会编译通过。这就是处理异常的好处。
public static void main(String[] args) System.out.println(readFile()); public static String readFile() File file = new File("d:/test.txt"); Scanner sc = null; try sc = new Scanner(file); catch (FileNotFoundException e) e.printStackTrace(); return sc.nextLine();

2.在方法上加上异常说明, 相当于将处理动作交给上级调用者
使用throws说明异常,则不需要在readFile方法内部使用try catch捕捉异常。如果main方法没有使用try catch捕捉异常则交给JVM处理,否则是自己捕捉异常。
public static void main(String[] args) try System.out.println(readFile()); catch (FileNotFoundException e) e.printStackTrace(); public static String readFile() throws FileNotFoundException File file = new File("d:/test.txt"); Scanner sc = new Scanner(file); return sc.nextLine();

JAVA SE—— 异常

文章图片

五、自定义异常类注意:
  1. 自定义异常类继承Exception类默认是受查异常
  2. 继承RuntimeException类默认是非受查异常。
用户登录代码实例
public class Test private static final String userName = "author"; private static final String password = "12138"; public static void login(String userName, String password) if(!Test.userName.equals(userName))// 用户名不相同,进入if语句 System.out.println("用户名输入错误!"); if (!Test.password.equals(password)) System.out.println("password输入错误!"); public static void main(String[] args) login("author","12138");

我们的想法是将自定义一个用户登录错误异常
此处一个异常类继承Exception还是RuntimeException的意义:
我们来举个例子:
假设自定义异常类继承的都是RuntimeException,则此处编译时不会报错,因为继承RuntimeException是非受查异常,则在运行时才会报错。
代码1:
class NameException extends RuntimeException public NameException(String name) super(name); class PassWordException extends RuntimeException public PassWordException(String message) super(message); public class Test private static String name = "hhh"; private static String password = "1234"; public static void login(String name, String password) throws NameException, PassWordException if (!Test.name.equals(name)) throw new NameException("名字错误"); if (!Test.password.equals(password)) throw new PassWordException("密码错误"); public static void main(String[] args) login("hhh", "123");

JAVA SE—— 异常

文章图片

假设自定义异常类继承的都是Exception,则默认自定义异常类为受查异常,再用上面的代码,只将自定义继承改为Exception。则编译时期就会报错。
class NameException extends Exception public NameException(String name) super(name); class PassWordException extends Exception public PassWordException(String message) super(message); public class Test private static String name = "bit"; private static String password = "1234"; public static void login(String name, String password) throws NameException, PassWordException if (!Test.name.equals(name)) throw new NameException("名字错误"); if (!Test.password.equals(password)) throw new PassWordException("密码错误"); public static void main(String[] args) login("bit", "123");

因为受查异常要用两种显示的方式进行处理,要不用try catch语句处理,要不抛出异常用throw和throws成对处理。
对于为何自定义一个异常类需要在构造方法中调用父类的构造方法。是因为我们可以在throw new NameException();时能够在括号中说明错的原因。而输入错的原因时又是一个字符串。我们在看Exception异常类的源码时,可以看到它也是继承了Throwable类。而其中也调用了构造方法。我们也已经知道了一个子类的构造方法是默认一个不带参数的调用父类的构造方法的。因此一切都能说通了。
JAVA SE—— 异常

文章图片

JAVA SE—— 异常

文章图片

注意事项:

    推荐阅读