一、异常简介
1.什么是异常
异常,就是对程序运行过程中,遇到的种种不正确的情况的描述。在JAVA中,异常使用Exception类来描述。如果程序遇到了未经处理的异常,将会导致程序无法编译或运行。
2.异常的继承体系
在Java程序中,异常的继承体系是基于java.lang.Throwable类的。Throwable类是所有异常和错误的父类。它有两个直接子类:Exception和Error。
1. Exception类: Exception类是所有可以被捕获并处理的异常的父类。它通常表示程序运行时出现的逻辑错误或异常情况,如IOException、SQLException等。Exception类又分为运行时异常和检查性异常:
运行时异常(RuntimeException):运行时异常是在程序运行过程中发生的异常,通常由程序员的错误或疏忽引起,例如NullPointerException、ArrayIndexOutOfBoundsException等。这些异常在代码编写和编译阶段可能不会被注意到,但在程序运行时会被抛出。运行时异常通常不需要显式地捕获和处理,因为它们代表了程序中的错误,应该被修正。
检查性异常(Checked Exception):检查性异常是在编译阶段被编译器检查的异常,通常表示程序可能遇到的错误情况,例如IOException、SQLException等。这些异常是API文档或方法声明中明确声明必须被捕获或声明抛出的异常。它们通常代表了一些外部资源的问题,比如文件访问错误或数据库连接问题,需要程序员主动处理这些异常,以确保程序的健壮性和容错性。
2. Error类: Error类是表示程序中严重错误的异常。这些错误通常不是由程序本身引起的,而是由于环境问题、JVM错误或资源耗尽等原因导致的,比如OutOfMemoryError、StackOverflowError等。Error通常被认为是不可恢复的,并且程序通常无法处理这些错误。
3. 自定义异常: 开发人员还可以创建自己的异常类,这些类继承自Exception或RuntimeException,以表示程序特定的错误情况。自定义异常类应该提供有意义的错误信息,并且可以根据需要提供处理这些错误的方法。
异常的继承体系允许程序员以一种结构化的方式处理程序中的错误情况,通过捕获特定类型的异常,程序员可以编写相应的错误处理逻辑,从而提高程序的可靠性和可维护性。
二、异常处理
1.异常处理的语法
try{
//将可能出现异常的代码放到这里执行
//一旦try中的代码出现了异常,则从出现的位置开始,到try的花括号的括回,这一段代码就不执行了
} catch (需要捕获的异常类型 标识符) {
//如果try中的代码出现了异常,并且异常对象的类型和捕获类型一致,在这里的代码就会执行,一旦一个异常被捕获,那么这个这个异常将不再影响程序的编译和执行。
}
代码示例:
public class Student { private String name="sun"; public static void main(String[] args) { try { Student[] students=new Student[2]; System.out.println(students[0].name); // Cannot read field "name" because "students[0]" is null System.out.println(students.length); } catch (Exception e) { throw new RuntimeException(e); } } }
2.多种异常的处理
try {
// 可能出现异常的代码
}catch (异常类型1 标识符) {
// 针对异常类型1的处理
}catch (异常类型2 标识符) {
// 针对异常类型2的处理
}catch (异常类型3 标识符) {
// 针对异常类型3的处理
}
...
需要注意异常的书写顺序。
如果多个catch的异常之间不存在继承关系,不需要考虑书写顺序。
如果多个catch的异常之间存在继承关系,则必须子类异常在前,父类异常在后。
如果处理的多种异常没有继承关系,并且处理方式相同,可以简写成如下语法
try {
// 可能会出现多种异常的代码
}catch (异常类型1 | 异常类型2 | ... 标识符) {
// 出现了多种异常之一,都可以被这里捕获
}
或者使用父类异常处理
try {
// 可能会出现多种异常的代码
}catch (Exception e) {
// 处理的代码逻辑都一样,就一种。
}
public class Test01 {
public static void main(String[] args) {
/**
* 不存在继承关系的异常类型名称
*/
try {
String str = null;
int len = str.length();
} catch(IndexOutOfBoundsException e) {
System.out.println("IndexOutOfBoundsException");
e.printStackTrace();
}
catch(NullPointerException e) {
System.out.println("NullPointerException");
e.printStackTrace();
}
catch(ArithmeticException e) {
System.out.println("ArithmeticException");
e.printStackTrace();
}
try {
String str = null;
int len = str.length();
} catch(IndexOutOfBoundsException | NullPointerException | ArithmeticException e) {
//不存在继承关系的可以用 | 来连接
e.printStackTrace();
}
// 存在继承关系的异常类型名
try {
FileInputStream fis = new FileInputStream("");
} catch(RuntimeException e) {
e.printStackTrace();
} catch(Exception e) {
e.printStackTrace();
}
}
}
3.finally模块
finally用在try后面,或者catch后面,作为异常捕获的结尾。
特点:finally中的语句始终会执行。(无论try中的代码是否出现了异常,这里的代码都会执行)
使用场景:会在finally中做资源释放、流的关闭等操作。
FileInputStream fis = null;
try {
fis = new FileInputStream("");
} catch(RuntimeException e) {
e.printStackTrace();
} catch(Exception e) {
e.printStackTrace();
} finally {
try{
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
* 研究一下finally和return的特点
* 1. 当try里有return关键字,以及finally模块没有return ,先执行finally模块代码,然后再执行try里的return关键字
* 2. try和finally里都有return, 一定执行的finally里的return.
4. 简述 final、finally、finalize 的区别
final、finally和finalize是Java语言中的三个不同的关键字,它们具有不同的含义和用途:
final:这个关键字有几个不同的用法:
用于声明类:类被声明为final意味着它不能被继承,即没有子类可以扩展这个类。这通常用于创建不可变类,例如Java的String类。
用于声明方法:方法被声明为final意味着它不能被覆写(Override)。这确保了方法的实现不会被继承它的子类更改。
用于声明变量:变量被声明为final意味着它的值一旦被初始化就不能被更改。
finally:这个关键字用于异常处理。finally块中的代码总是在try块(包含可能抛出异常的代码)执行完毕后执行,无论try块中的代码是否抛出异常。finally块通常用于清理资源,例如关闭文件或者释放数据库连接。
finalize:这是一个方法名,它是Object类的一个方法。在Java中,finalize()方法在垃圾回收器确定没有对该对象的引用时被调用。这个方法允许对象在被垃圾回收之前执行一些清理操作。然而,由于垃圾回收器的不确定性,finalize()方法并不能保证什么时候被调用,或者是否会被调用,因此它不是一种可靠的清理资源的方式。并且,在现代Java编程中,通常不建议使用finalize()方法,而是推荐使用try-with-resources语句或者在代码中显式地释放资源。
总结来说,final用于创建不可变的类、方法和变量;finally用于确保在try块执行后执行清理操作;而finalize是Object类的一个方法,用于在对象被销毁之前执行清理操作,但由于其不确定性,使用时需要谨慎。
三、自定义异常
1.为什么要自定义异常
如果系统给我们提供的异常类型,已经不能满足我们的需求了,或者不知道用哪个了。此时就需要进行异常的自定义。
2.怎么自定义异常
通过阅读异常源代码:发现java中所有的异常类,都是继承Throwable,或者继承Throwable的子类。这样该异常才可以被throw抛出。说明这个异常体系具备一个特有的特性:可抛性:即可以被throw关键字操作。
并且查阅异常子类源码,发现每个异常中都调用了父类的构造方法,把异常描述信息传递给了父类,让父类帮我们进行异常信息的封装。
public class NullPointerException extends RuntimeException {
public NullPointerException() {
super();//调用父类构造方法
}
public NullPointerException(String s) {
super(s);//调用父类具有异常信息的构造方法
}
}
异常书写格式如下:
Class 异常名 extend Exception{
public 异常名(){
}
pulbic 异常名(String s){
super(s);
}
}
代码示例:
/**
* 定义Person类,包含name与age两个成员变量。
* 在Person类的有参数构造方法中,进行年龄范围的判断,
* 若年龄为负数或大于120岁,则抛出NoAgeException异常,异常提示信息“年龄数值非法”。
*/
public class Test01 {
public static void main(String[] args) {
Person person = new Person("张三", 100);
System.out.println(person);
Person person1 = new Person("李四", 121);
System.out.println(person1);
}
}
class Person{
private String name;
private int age;
public Person(String name, int age) throws NoAgeException{
this.name = name;
if (age < 0 || age > 120) {
throw new NoAgeException("年龄数值非法");
}
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
//自定义异常类
class NoAgeException extends RuntimeException{
public NoAgeException(){
super();
}
public NoAgeException(String message){
super(message);
}
}
3.throw和throws关键字
throw:
用在程序方法中,表示抛出异常。一个异常对象实例化完成后,没有任何意义,只有当把这个异常抛出之后,这个异常才会生效,具有阻止程序的编译或者运行的意义。
throws:
1. 书写位置:在方法的定义格式中,也就是形参列表的后面。用于声明异常类型。
修饰词 返回值类型 方法名(形参列表) throws 异常类型名{
}
2. 当我们throw的是RuntimeException类的异常时,不需要throws来声明异常类型, 声明没有意义,JVM运行程序才能知道。
3. 当我们throw的是Exception异常时,必须使用throws来声明异常类型,用来告知 谁调用这个方法,谁处理异常。
4.常见的异常
NullPointerException 空指针
ArrayIndexOutOfBoundsException 数组越界
ArithmeticException 算法错误
InputMismatchException 输入数据有错
ParseException 解析错误ClassCastException 类型转换异常