异常的背景
初识异常
异常是程序的运行过程中出现的一种错误
编译期:在编译过程中,如果编译通过,一定不存在编译错误
运行时:在运行过程中,现在没有错误,不代表后面就没有错误;在自己的机器上没有错误,不代表在别人机器上没有错误…
数学上证明:没有办法证明一段程序是没有 bug 的
异常其实是帮助我们解决问题的一种很好的手段
异常的种类有很多,分别代表不同的含义,一旦出现某个异常,此时这个异常的意义是明确的,明确的表现出出现异常的原因
初以0
数组下标越界
空指针异常
防御式编程
“未言胜先言败”
防御是编程有两种具体代码的具体形式
英雄联盟开局的伪代码
LBYL
Look Before You 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 (载入游戏画面异常) {
处理载入游戏画面异常;
}
............
异常的基本用法
捕捉异常
异常主要涉及到的几个关键字:
- try: try 语句块中放置可能会抛出异常的代码
- catch:语句块中放置用来处理异常的代码. try 和 catch 往往要搭配使用,当 try 中出现异常的事后,就会进入 catch 中执行
- throw:主动抛出异常对象(Java 的异常本质上就是一个一个的对象)
- throws:某个方法可能会抛出某些异常
- finally:一般用于异常处理完毕后的收尾工作
基本语法:
try{
有可能出现异常的语句 ;
} catch (异常类型 异常对象) {
} ...
finally {
异常的出口
}
- try 代码块中方的是可能出现异常的代码块
- chtch 代码块中放的是出现异常后的处理行为
- finally 代码块中的代码块用于处理善后工作,会在最后执行
- 其中 catch 和 finally 都可以根据实际情况原则加或者不加
例1 :
如果代码中出现了异常,并且没有使用 try catch 异常就会有由JVM 自己来处理,程序就会被直接终止
int[] a = null;
System.out.println(a[0]);
例2 :
代码中可以使用 try 把可能抛出异常的代码给包裹起来,使用 catch 来处理这样的异常
public static void main(String[] args) {
try {
System.out.println("try 中异常之前代码");
int[] a = null;
System.out.println(a[0]);
System.out.println("catch 中异常之后的代码");
} catch (NullPointerException e){
System.out.println("catch 中的代码");
}
System.out.println("hello");
}
try catch 的执行顺序“
- 先执行 try 中的代码块(按顺序执行)
- 执行 try 代码的过程,如果出现异常,就会进入到 catch 执行。try 中剩下的代码就不再执行了
- 当 catch 也执行完毕之后,就会继续执行后续的代码,程序没有异常终止
常见的 “错误分级” 体系:
- Fatal :最严重的
- Error :比较严重的
- Warning :不太严重的
- Info / Notice :没啥事的
例3 :
public static void main(String[] args) {
try {
System.out.println("try 中异常之前代码");
int[] a = {1, 2, 3};
System.out.println(a[100]);
System.out.println("catch 中异常之后的代码");
} catch (NullPointerException e){
System.out.println("catch 中的代码");
}
System.out.println("hello");
}
catch 中的异常的类型需要和抛出的异常类型匹配,才能够正确的处理,否则执行不到 catch 中的逻辑
如果 try 中抛出的异常的类型和 catch 中声明的类型不匹配,才是 catch 中代码就不会被执行到
使用 try catch 的时候,必须要非常明确的知道,try 中都会抛出哪些异常
例4 :
如果 try 中可能抛出多种异常的话,也就需要多个 catch 语句来进行处理
1 个 try 可以对应 N 个 catch
多个 catch 之间就好像多分支语句一样
public static void main(String[] args) {
try {
System.out.println("try 中异常之前代码");
int[] a = {1, 2, 3};
System.out.println(a[100]);
System.out.println("catch 中异常之后的代码");
} catch (NullPointerException e){
System.out.println("catch 空指针异常");
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("catch 下标越界异常");
}
System.out.println("hello");
}
例5 :
可以使用一个 catch 语句来捕捉多个异常
使用 | 就可以把多好异常的类型并列起来,相当于 “逻辑或”
抛出这若干个异常中的任何一个,都会触发 catch
public static void main(String[] args) {
try {
System.out.println("try 中异常之前代码");
int[] a = {1, 2, 3};
System.out.println(a[100]);
System.out.println("catch 中异常之后的代码");
} catch (NullPointerException | ArrayIndexOutOfBoundsException e){
System.out.println("catch 异常");
}
}
或者使用更高级别的父类,空指针异常和数组越界异常都是 Exception 的子类
public static void main(String[] args) {
try {
System.out.println("try 中异常之前代码");
int[] a = {1, 2, 3};
System.out.println(a[100]);
System.out.println("catch 中异常之后的代码");
} catch (Exception e){
System.out.println("catch 异常");
}
}
例6 :
使用 finally
public static void main(String[] args) {
try {
System.out.println("try 中异常之前代码");
int[] a = {1, 2, 3};
System.out.println(a[100]);
System.out.println("catch 中异常之后的代码");
} catch (Exception e){
System.out.println("catch 异常");
} finally {
System.out.println("hello");
}
}
例7 :
使用 finally 回收资源
public static void main(String[] args) {
Scanner scanner = null;
try {
scanner = new Scanner(System.in);
} catch (NullPointerException e){
System.out.println("空指针异常");
} finally {
scanner.close();
}
}
使用 try with resource 执行回收资源
代码执行完之后会自动释放 Scanner 的空间
public static void main(String[] args) {
try (Scanner scanner = new Scanner(System.in)) {
} catch (NullPointerException e) {
System.out.println("空指针异常");
}
}
例8 :
如果当前方法没有适合的 catch ,异常就会沿着调用栈向上传递
public static void main(String[] args) {
try {
func1();
} catch(ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
} finally {
System.out.println("finally");
}
System.out.println("hello");
}
public static void func1() {
func2();
}
public static void func2() {
int[] a = {1, 2, 3};
System.out.println(a[100]);
}
如果异常向上传递的过程中,移植到了最上面之后还是没有遇到 catch ,此时异常就会由 JVM 自己来处理,程序就会直接终止
例9 :
在 final 代码块中谨慎使用 return
public static void main(String[] args) {
System.out.println(func());
}
public static int func() {
try {
return 10;
} final {
return 20;
}
}
Java 异常体系
- 顶层类 Throwable 派生出两个重要的子类, Error 和 Exception
- 其中 Error 指的是 Java 运行时内部错误和资源耗尽错误. 应用程序不抛出此类异常. 这种内部错误,一旦出现,除了告知用户并使程序终止之外, 再无能无力. 这种情况很少出现
- Exception 是我们程序猿所使用的异常类的父类
- 其中 Exception 有一个子类称为 RuntimeException , 这里面又派生出很多我们常见的异常类NullPointerException , IndexOutOfBoundsException 等
Java 语言规范将派生于Error 类或 RuntimeException 类的所有异常称为 非受查异常, 所有的其他异常称为 受查异常
如果是 受查异常,就必须显式处理
使用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();
}
在方法上加上异常说明,相当于将处理动作交给上级调用者
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();
}
自定义异常类
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入用户名:");
String name = scanner.next();
try {
name(name);
} catch (NameException e) {
e.printStackTrace();
}
boolean result = !name.equals("admin");
//判断输入的用户名是否正确,如果不正确抛出异常且结束运行
if(result) {
return;
}
System.out.println("请输入密码:");
String password = scanner.next();
try {
password(password);
} catch (PasswordException e) {
e.printStackTrace();
}
}
public static void name(String name) throws NameException {
if (!name.equals("admin")) {
throw new NameException("用户名错误");
}
}
public static void password(String password) throws PasswordException {
if (!password.equals("123456")) {
throw new PasswordException("密码错误");
}
}
注:
- 自定义异常通常会继承自 Exception 或者 RuntimeException
- 继承自 Exception 的异常默认是受查异常
- 继承 RuntimeException 的异常默认是非受查异常