异常(超详细)
一:异常体系介绍
(1)异常:异常就是代表程序出现的问题
误区:不是让我们以后不出异常,而是程序出了异常后,该如何处理
(2)在Java当中其实有很多很多异常,这些异常组成了异常的继承体系,最上层的时throwable,下面有两个子类Error和Exception。在Exception异常的下面还有两个分类,RuntimeException和其他异常,我们之前的ArithmeticException、ArrayIndexOutOfBoundsException都是RuntimeException的子类。
Error:代表的系统级别错误(属于严重问题),比如内存溢出,这就是你由于内存不足导致的错误,是属于硬件的问题,我们代码层面不需要管,也管不了。系统一旦出现问题,sun公司会把这些错误封装成Error对象。Error是给sun公司自己用的,不是给我们程序员用的。因此我们开发人员不用管它。
Exception:叫做异常,代表程序可能出现的问题。我们通常会用Exception以及它的子类来封装程序出现的问题。
运行时异常:RuntimeException及其子类,编译阶段不会出现异常提醒。运行时出现的异常(如:数组索引越界)
编译时异常:编译阶段就会出现异常提醒的。(如:日期解析异常)
(3)小结:
1.异常是什么?
程序中可能出现的问题
2.异常体系的最上层父类是谁?异常分为几类?
父类:Exception
异常分为两类:编译时异常、运行时异常
3.编译时异常和运行时异常的区别?
编译时异常:没有继承RuntimeException的异常,直接继承于Exception。编译阶段就会错误提示。
运行时异常:RuntimeException本身和子类。编译阶段没有错误提示,运行时出现的
二:编译时异常和运行时异常
(1)看看下面这段代码
public class ExceptionTest1 {
public static void main(String[] args) throws ParseException {
// 编译时异常(在编译阶段,必须要手动处理,否则代码报错)
String time = "2030年1月1日";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日");
Date date = sdf.parse(time); // parse下面会有一个红色波浪线
System.out.println(date); // Tue Jan 01 00:00:00 CST 2030
// 运行时异常(在编译阶段不需要处理,是代码运行时出现的异常)
int[] arr = {
1, 2, 3, 4};
System.out.println(arr[10]); // ArrayIndexOutOfBoundsException
}
}
一开始我们写的是Java文件,如果说我想要运行代码,首先是通过JavaC进行编译,编译成字节码文件,上面这个就叫做编译阶段,在这个阶段中要处理的异常就叫做编译时异常,比如上面的parseException日期解析异常,他就是一个编译时期异常,你不处理,代码就一直报错,这就是它最大的特点。编译完了之后,我们需要通过Java去运行代码,那么在运行的时候出现的异常就是运行时异常,它是RuntimeException本身或者是它的子类。运行时期异常在编译阶段不需要处理,是代码运行的时候出现的异常,比如刚刚的数组索引越界异常,只有代码运行的时候才会出现。
(2)Java为什么要这么设计?为什么要分为编译时期异常和运行时期异常?为什么不能把所有的异常都归为一类呢?
在编译的时候Java是不会运行代码的,他只会检查语法是否错误或者是做一些性能的优化。比如说定义变量类型错了,这是语法错误,在编译的时候Java会检查,再比如说字符串拼接的优化机制,在编译之后等号的右边就变成了最终的结果String s = "abc"了(原来是Stirng s = “a” + “b” + “c”),这是性能的优化。但是如果遇到索引越界,在编译的时候它是不知道的。比如下面这段代码:
int[] arr = new int[随机数];
int num = arr[10];
只有代码真正运行了,我才知道索引的长度,才能确定索引是否超出范围,像这种只能在运行的时候发生的异常,能放到编译时吗?
(3)那为什么不都放在运行时异常RuntimeException下面呢?
因为编译时期异常更多的是在于提醒程序员检查本地信息,比如日期解析异常,parse方法的底层会涉及到本地操作系统当中时区的相关信息。再比如说用IO去读取本地文件的数据,而这个文件他也是在本地操作系统当中的,用来提醒程序员去检查这个文件是不是真的存在。总而言之,编译时期异常是在于提醒程序员去检查本地信息,告诉程序员如果有问题就会出这个异常。而运行时期异常,它的核心不在于提醒,他就是由于代码出错而导致程序出现的问题。
(4)小结:
1.运行时异常和编译时异常的区别?
1)编译时异常:除了RuntimeException和它的子类,其他类都是编译时异常。编译阶段需要进行处理,作用在于提醒程序员检查本地信息。
2)运行时异常:RuntimeException本身和所有子类,都是运行时异常。编译阶段不报错,是程序运行时出现的。一般是由于参数传递错误带来的问题。
三:异常在代码中的两个作用
(1)作用一:异常是用来查询bug的关键参考信息
public class ExceptionDemo2 {
public static void main(String[] args) {
Student[] arr = new Student[3]; // null null null
String name = arr[0].getName();
System.out.println(name); // NullPointerException
}
}
class Student {
private String name;
private int age;
// get set等方法省略...
}
(2)作用二:异常可以作为方法内部的一种特殊的返回值,以便通知调用者底层的执行情况
public class ExceptionDemo3 {
public static void main(String[] args) {
Student2 student2 = new Student2();
student2.setAge(99); //年龄超出范围
System.out.println(student2);
}
}
class Student2 {
private String name;
private int age;
public void setAge(int age) {
if (age < 18 || age > 40) {
System.out.println("年龄超出范围");
} else {
this.age = age;
}
}
}
在上面这个创建学生对象中,给学生对象的年龄赋值,在这个过程当中,他一共有三个角色。第一个是JavaBean的Student代码,还有main方法的调用处是调用setAge()方法的,还有第三个是控制台。在一开始是调用处调用了Student里面的代码,在上面的代码中,Student类并没有将赋值的结果告诉给调用者,而是直接打印在了控制台。这个时候站在调用者的角度去思考,他根本不知道赋值的结果。这个时候怎么办?此时就可以用异常去解决,在setAge()这个方法中,直接给调用者返回一个异常,此时调用者就知道当前的赋值情况了。得到异常之后,可以选择打印在控制台或者是自己单独处理。
public class ExceptionDemo3 {
public static void main(String[] args) {
Student2 student2 = new Student2();
student2.setAge(99); //RuntimeException
System.out.println(student2);
}
}
class Student2 {
private String name;
private int age;
public void setAge(int age) {
if (age < 18 || age > 40) {
throw new RuntimeException();
// 这个时候,它的结果就不会