Java 异常
异常简介
异常概念
异常,就是程序出现了不正常的情况。
如果程序出现了问题,我们没有做任何处理,那么最终 JVM 会做默认的处理,其处理方式有如下两个步骤:
- 把异常的名称、错误原因及异常出现的位置等信息输出在了控制台。
- 程序停止执行。
控制台在打印异常信息时,会打印异常类名、异常出现的原因、异常出现的位置等信息。我们在 debug 时,可以根据提示,找到异常出现的位置,分析原因,修改异常代码。
异常体系
除了 RuntimeException
和其子类,以及 error
和其子类,其他所有异常都是 checkedException
。
- Error:是程序无法处理的错误,表示运行应用程序中较严重问题。
- 大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。
- 这些错误是不可检查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况,比如 OutOfMemoryError 和 StackOverflowError 等。
- Exception:是需要通过代码处理(声明或捕获)的。
如何区分 Error 与 Exception:
- 如果程序出现了不正常的信息的类名是以 Error 结尾的(除了一个 ThreadDeath),那么肯定是一个错误。
- 如果是以 Exception 结尾的,那么肯定是一个异常。
编译时异常和运行时异常
-
编译时异常(CheckedException):
- 都是 Exception 类及其子类。
- 如果一个方法内部抛出了一个编译时异常对象,那么方法上一定要声明,调用者也必须处理。
- 除了运行时异常,都是编译时异常。
-
运行时异常(RuntimeException):
- 都是 RuntimeException 类及其子类。
- 如果一个方法内部抛出了一个运行时异常对象,那么方法上可以声明也可以不声明,调用者可以处理也可以不处理。
- 问:为什么 JAVA 编译器会如此严格要求编译时异常,对运行时异常如此宽松?
- 因为运行时异常都是可以通过良好的编程习惯去避免(如事先进行条件判断语句)。
- 而编译时异常很多时候是没法通过编程去避免的(如磁盘不够导致 I/O 出错等)。
常见异常
RuntimeException:
序号 | 异常名称 | 异常描述 |
---|---|---|
1 | ArrayindexOutOfBoundsException | 数组越界异常 |
2 | NullPointerException | 空指针异常 |
3 | IllegalArqumentException | 非法参数异常 |
4 | NegativeArraySizeException | 数组长度为负异常 |
5 | llleaalStateException | 非法状态异常 |
6 | ClassCastException | 类型转换异常 |
UncheckedException:
序号 | 异常名称 | 异常描述 |
---|---|---|
1 | NoSuchFieldException | 表示该类没有指定名称抛出来的异常 |
2 | NoSuchMethodException | 表示该类没有指定方法抛出来的异常 |
3 | IllegalAccessException | 不允许访问某个类的异常 |
4 | ClassNotFoundException | 类没有找到执出异常 |
throws 方式处理异常
格式:
public void 方法() throws 异常类名 {
}
-
编译时异常必须要显式进行处理,有两种处理方案:try … catch … 或者 throws。如果采用 throws 这种方案,那么要在方法上进行显示声明,将来谁调用这个方法谁就要处理。
-
运行时异常因为在运行时才会发生,所以在方法后面可以不写,出现运行时异常时则默认交给 JVM 处理。
示例:
public class ExceptionDemo {
public static void main(String[] args) throws ParseException{
System.out.println("开始");
// method();
method2();
System.out.println("结束");
}
// 编译时异常(日期格式解析错误)
public static void method2() throws ParseException {
String s = "2048-08-09";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date d = sdf.parse(s);
System.out.println(d);
}
// 运行时异常(索引越界)
public static void method() throws ArrayIndexOutOfBoundsException {
int[] arr = {1, 2, 3};
System.out.println(arr[3]);
}
}
try … catch 方式处理异常
格式:
try {
可能出现异常的代码;
} catch(异常类名 变量名) {
异常的处理代码;
}
执行流程:
- 程序从 try 里面的代码开始执行。
- 出现异常,就会跳转到对应的 catch 里面去执行。
- 执行完毕之后,程序还可以继续往下执行。
示例代码:
public class ExceptionDemo {
public static void main(String[] args) {
System.out.println("开始");
method();
System.out.println("结束");
}
public static void method() {
try {
int[] arr = {1, 2, 3};
System.out.println(arr[3]);
System.out.println("这里能够访问到吗");
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("你访问的数组索引不存在,请回去修改为正确的索引");
}
}
}
注意:
-
如果 try 中没有遇到问题,怎么执行?
- 会把 try 中所有的代码全部执行完毕,不会执行 catch 里面的代码。
-
如果 try 中遇到了问题,那么 try 下面的代码还会执行吗?
- 那么会直接跳转到对应的 catch 语句中,try 中接下来的代码就不会再执行了。
- 当 catch 里面的语句全部执行完毕,表示整个体系全部执行完全,继续执行下面的代码。
-
如果出现的问题没有被捕获,那么程序如何运行?
- 那么 try...catch 就相当于没有写,也就是自己没有处理,默认交给虚拟机处理。
-
同时有可能出现多个异常怎么处理?
- 出现多个异常,那么就写多个 catch 就可以了。
- 注意点:如果多个异常之间存在子父类关系,那么父类一定要写在下面(若从子到父,后面的异常即是废话)。
finally
finally 保证了 try...catch 代码块中即使有异常,也能执行 finally 代码块。
public static void main(String[] args) {
try{
int i = 1/0;
} catch (Exception e) {
e.printStackTrace();
throw e; // 即使在catch又有异常,finally代码块也能执行
} finally {
System.out.println("无finally会执行吗");
}
}
总结:
- 与 finally 相对应的 try 语句得到执行的情况下,finally 才有可能执行。
- finally 执行前,若程序或线程终止,则 finally 不会执行。
throw 抛出异常
格式:throw new 异常();
注意:
- 如果一个方法的内部抛出了一个编译时异常对象,那么必须要在此方法上声明抛出。
- 如果调用了一个声明抛出异常的方法,那么调用者必须要处理。
- 如果一个方法内部抛出了一个异常对象,那么 throw 语句后面的代码都不会执行了(一个方法遇到 throw 语句,这个方法也会马上停止执行)。
- 在一种情况(条件)下,只能抛出一个异常对象。
throws 和 throw 的区别:
throws | throw |
---|---|
用在方法声明上 | 用在方法内部 |
后跟异常类型 | 后跟异常对象 |
后可声明多个异常类型的异常 | 后只能有一个异常对象 |
示例代码:
public class ExceptionDemo8 {
public static void main(String[] args) {
//int [] arr = {1,2,3,4,5};
int [] arr = null;
printArr(arr); // 此行会接收到一个异常,因此在此还需要自己处理一下异常
}
private static void printArr(int[] arr) {
if(arr == null){
// 调用者知道成功打印了吗?
System.out.println("参数不能为null");
// 当参数为 null 的时候,手动创建了一个异常对象,抛给调用者
throw new NullPointerException();
}else{
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}
}
Throwable 成员方法
常用方法:
方法名 | 说明 |
---|---|
public String getMessage() | 返回此 throwable 的详细消息字符串 |
public String toString() | 返回此可抛出的简短描述 |
public void printStackTrace() | 把异常的错误信息输出在控制台 |
示例代码:
public class ExceptionDemo {
public static void main(String[] args) {
System.out.println("开始");
method();
System.out.println("结束");
}
public static void method() {
try {
int[] arr = {1, 2, 3};
System.out.println(arr[3]); // new ArrayIndexOutOfBoundsException();
System.out.println("这里能够访问到吗");
} catch (ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
// public String getMessage():返回此 throwable 的详细消息字符串
System.out.println(e.getMessage()); //Index 3 out of bounds for length 3
// public String toString():返回此可抛出的简短描述
System.out.println(e.toString()); // java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
// public void printStackTrace():把异常的错误信息输出在控制台
e.printStackTrace(); // 打印信息如下:
// java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
// at com.itheima_02.ExceptionDemo02.method(ExceptionDemo02.java:18)
// at com.itheima_02.ExceptionDemo02.main(ExceptionDemo02.java:11)
}
}
}
自定义异常
当 Java 中提供的异常不能满足我们的需求时,我们就可以自定义异常。
实现步骤:
- 定义异常类
- 写继承关系
- 提供空参构造
- 提供带参构造
代码实现:
- 异常类:
public class AgeOutOfBoundsException extends RuntimeException {
public AgeOutOfBoundsException() {
}
public AgeOutOfBoundsException(String message) {
super(message);
}
}
- 学生类:
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
if(age >= 18 && age <= 25){
this.age = age;
}else{
//如果Java中提供的异常不能满足我们的需求,我们可以使用自定义的异常
throw new AgeOutOfBoundsException("年龄超出了范围");
}
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
- 测试类:
public class ExceptionDemo12 {
public static void main(String[] args) {
// 键盘录入学生的姓名和年龄,其中年龄为 18 - 25岁
// 超出这个范围是异常数据不能赋值,需要重新录入,一直录到正确为止。
Student s = new Student();
Scanner sc = new Scanner(System.in);
System.out.println("请输入姓名:");
String name = sc.nextLine();
s.setName(name);
while(true){
System.out.println("请输入年龄:");
String ageStr = sc.nextLine();
try {
int age = Integer.parseInt(ageStr);
s.setAge(age);
break;
} catch (NumberFormatException e) {
System.out.println("输入有误,请输入一个整数");
continue;
} catch (AgeOutOfBoundsException e) {
System.out.println(e.toString());
System.out.println("输入有误,请输入一个符合范围的年龄");
continue;
}
/*if(age >= 18 && age <=25){
s.setAge(age);
break;
}else{
System.out.println("输入有误,请输入符合要求的年龄");
continue;
}*/
}
System.out.println(s);
}
}