目录
一、异常
所谓异常指的就是程序在 运行时 出现错误时通知调用者的一种机制。
关键字 :"运行时"有些错误是这样的, 例如将 System.out.println 拼写错了, 写成了 system.out.println. 此时编译过程中就会出错, 这是 "编译期" 出错;
而运行时指的是程序已经编译通过得到 class 文件了, 再由 JVM 执行过程中出现的错误。
1.常见异常
1、算术异常(ArithmeticException)
例1:除以0
System.out.println(10 / 0);
// 执行结果:Exception in thread "main" java.lang.ArithmeticException: / by zero
2、数组下标越界(ArrayIndexOutOfBoundsException)
例2:
int[] arr = {1, 2, 3};
System.out.println(arr[100]);
// 执行结果:Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 100
3、空指针异常NullPointerException
例3:访问 null 对象
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
2.防御式编程
错误在代码中是客观存在的,因此我们要让程序出现问题的时候及时通知程序猿;我们有两种主要的方式:
LBYL: Look Before You Leap. 在操作之前就做充分的检查. EAFP: It's Easier to Ask Forgiveness than Permission. "事后获取原谅比事前获取许可更容易". 也就是先操作, 遇到 问题再处理.
- 异常的核心思想就是 EAFP.
二、异常的基本用法
1、捕获异常
(1)基本语法
try{
有可能出现异常的语句 ;
}[catch (异常类型 异常对象) {
} ... ]
[finally {
异常的出口
}]
- 注:
- try 代码块中放的是可能出现异常的代码.
- catch 代码块中放的是出现异常后的处理行为.
- finally 代码块中的代码用于处理善后工作, 会在最后执行.
- 其中 catch 和 finally 都可以根据情况选择加或者不加.
(2)不处理异常及使用捕获异常对比
(1)不处理异常
若不处理异常,一旦出现异常, 程序立刻终止,异常处之后不再运行;
int[] arr = {1, 2, 3};
System.out.println("语句1");
System.out.println(arr[100]);//发生异常处
System.out.println("语句2");
//运行结果:
语句1
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 100
at exception.TestDemo.main(TestDemo.java:7)
(2)使用 try catch 后
一旦 try 中出现异常, 那么 try 代码块中 从异常处 开始就不会执行, 而是交给 catch 中的代码来执行,catch 执行完毕会继续往下执行。
int[] arr = {1, 2, 3};
try {
System.out.println("语句1");
System.out.println(arr[100]);//异常出现处
System.out.println("语句2");
} catch (ArrayIndexOutOfBoundsException e) {//与上面异常匹配的catch
}
System.out.println("语句3");
// 语句1
语句3
(3)使用注意
① 在catch中可以打印出现异常的调用栈,可以得知异常类型,如下:
e.printStackTrace();
② catch 只能处理对应种类的异常;如下 catch 语句不能捕获到算术异常,因为异常类型不匹配,语句3也不能输出。
try {
System.out.println("语句1");
System.out.println(10/0);//异常处,算术异常
System.out.println("语句2");
} catch (ArrayIndexOutOfBoundsException e) {
// 打印出现异常的调用栈
e.printStackTrace();
}
System.out.println("语句3");
//语句1
Exception in thread "main" java.lang.ArithmeticException: / by zero
at exception.TestDemo.main(TestDemo.java:8)
③ catch 可以有多个;
一段代码可能会抛出多种不同的异常, 不同的异常有不同的处理方式. 因此可以搭配多个 catch 代码块。
int[] arr = {1, 2, 3};
try {
System.out.println("语句1");
System.out.println(arr[100]);
System.out.println("语句2");
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("这是个数组下标越界异常");
e.printStackTrace();
} catch (NullPointerException e) {
System.out.println("这是个空指针异常");
e.printStackTrace();
}
System.out.println("语句3");
//语句1
这是个数组下标越界异常
语句3
java.lang.ArrayIndexOutOfBoundsException: 100
at exception.TestDemo.main(TestDemo.java:8)
如果多个异常的处理方式是完全相同, 也可以写成这样
catch (ArrayIndexOutOfBoundsException | NullPointerException e) {
...//相同的处理方式
}
④ finally 表示最后的善后工作, 例如释放资源
- 建议一定不要在finally中 return!!!
如下:无论是否存在异常, finally 中的代码一定都会执行到,保证最终一定会执行到 Scanner 的 close 方法。
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();
} finally {
System.out.println("finally code");
}
// 执行结果
before
java.lang.NullPointerException
at demo02.Test.main(Test.java:12)
finally code
⑤ 使用 try 负责回收资源
刚才的代码可以有一种等价写法, 将 Scanner 对象在 try 的 ( ) 中创建, 就能保证在 try 执行完毕后自动调用 Scanner 的 close 方法
try (Scanner sc = new Scanner(System.in)) {
int num = sc.nextInt();
System.out.println("num = " + num);
} catch (Exception e) {
e.printStackTrace();
}
⑥如果本方法中没有合适的处理异常的方式, 就会沿着调用栈向上传递,如果向上一直传递都没有合适的方法处理异常, 最终就会交给 JVM 处理, 程序就会异常终止(和我们最开始 未使用 try catch 时是一样的)。
2、异常处理流程
- 程序先执行 try 中的代码;
- 如果 try 中的代码出现异常, 就会结束 try 中的代码, 看和 catch 中的异常类型是否匹配;
- 如果找到匹配的异常类型, 就会执行 catch 中的代码;
- 如果没有找到匹配的异常类型, 就会将异常向上传递到上层调用者;
- 无论是否找到匹配的异常类型, finally 中的代码都会被执行到(在该方法结束之前执行);
- 如果上层调用者也没有处理的了异常, 就继续向上传递;
- 一直到 main 方法也没有合适的代码处理异常, 就会交给 JVM 来进行处理, 此时程序就会异常终止。
3、自定义异常类
1.自定义异常通常会继承自 Exception 或者 RuntimeException;
2.继承自 Exception 的异常默认是受查异常(编译时);
3.继承自 RuntimeException 的异常默认是非受查异常(运行时)。
代码示例1:继承 RuntimeException 的异常
class MyException extends RuntimeException{//继承之后就是一个运行时异常
public MyException(String message) {
super(message);
}
}
public class Test {
public static void main(String[] args) {
try {
throw new MyException("exception");
}catch (MyException e){
e.printStackTrace();
}
}
}
运行结果:
代码示例2:继承 Exception 的异常
自定义异常类示例:用户登陆功能.
自定义两个异常,处理用户名出错和密码出错的问题:
接着是处理异常的两种情况:throw是声明异常,try-catch是处理异常,
1、login中声明,在main中进行异常的处理(右图);
2、login中处理异常,main中直接调用(左图)。
图左登录示例:
import java.util.Scanner;
class UserException extends Exception {//处理用户名出错
public UserException(String message) {
super(message);
}
}
class PasswordException extends Exception {//处理密码出错
public PasswordException(String message) {
super(message);
}
}
public class Test {
private static String userName = "August";
private static String password = "0802";
public static void login(String userName, String password) {
if (!Test.userName.equals(userName)) {
try {
throw new UserException("用户名错误");
} catch (UserException userException) {
userException.printStackTrace();
}
}
if (!Test.password.equals(password)) {
try {
throw new PasswordException("密码错误");
} catch (PasswordException passwordException) {
passwordException.printStackTrace();
}
}
if(Test.userName.equals(userName)&&Test.password.equals(password)){
System.out.println("登陆成功");
}
}
public static void main(String[] args) {
String name;
String key;
Scanner sc=new Scanner(System.in);
System.out.print("请输入用户名:");
name=sc.nextLine();
System.out.print("请输入密码:");
key=sc.nextLine();
login(name, key);
}
}
运行结果示例(三次):