目录
在java程序《运行》过程中,出现的不正常情况,出现的错误,称为异常
异常就是一个对象,描述那些不符合生活正常情况的异常情况,包含了这些情况的原因、类型、描述以及位置,这些内容都封装到异常对象中。
异常也是一种处理异常情况的机制,可以进行跳转、捕获以及结束程序,提供了程序退出的安全通道
Throwable:可抛出的,是异常体系的顶层父类,其它的异常或者错误都是Throwable的子类类型,只有Throwable的体系类型,才可以使用异常的处理机制
Error:错误,是Throwable的子类,用于描述那些无法捕获和处理的错误情况,属于非常严重的错误,StackOverflowError
Exception:异常,是Throwable的子类,用于描述那些可以捕获和处理的例外情况,属于不太严重的错误ArrayIndexOutOfBoundsException,NullPointerException
RuntimeException:运行时异常,是Exception的特殊的子类,在编译阶段不做检查的一个异常
这种异常我们不需要处理,由虚拟机掌管,比如NullPointerException,ArrayIndexOutOfBoundsException
体系图:
演示代码:
/**
* 在jvm中 默认处理异常的机制
*/
public class Simple01 {
?
? ?public static void main(String[] args) {
? ? ? ?test1();
? }
?
? ?public static void test1() {
? ? ? ?test2();
? }
?
? ?public static void test2() {
? ? ? ?test3();
? }
?
? ?public static void test3() {
? ? ? ?int i = 1 / 0;
? ? ? ?System.out.println(i);
? }
}
在代码的某个位置,出现了和正常情况不同的情况,就将异常情况封装到一个异常对象中。
将异常对象抛给调用该方法的方法,A调用B, B有问题就把异常抛给A。
某个方法接收到底层方法抛上来的异常,也没有办法自己处理,继续向上抛出,最终抛给main方法,main方法也没有办法处理,抛给调用自己的jvm虚拟机
Jvm虚拟机是我们手动调用的,只能将异常对象的所有信息,通过错误信息打印出来,结束jvm虚拟机
总结,jvm默认处理的方式:【一层一层向上抛,jvm接收到之后结束自己】
异常的声明:某个方法有编译时异常,编译就会无法通过,需要在异常所在的方法声明上,声明该方法可能出现的编译时异常
public ?static void ?test () throws FileNotFoundException
{
FileInputStream fileInputStream = new FileInputStream("/Users/lihongyan/Desktop/未命名.html");
}
?
JDK11新增方式
Path path = Paths.get("/Users/lihongyan/Desktop/未命名.html");
String data = Files.readString(path);
System.out.println(data);
异常的捕获:出现异常之后,可以通过某些格式来捕获,可以让程序在出现异常之后,继续运行,可以定义自己处理异常的逻辑。
捕获处理异常的代码格式:
try...catch
try...catch...finally
try...finally(无法捕获处理异常)
格式
try ? {
? ? ? 可能发生异常的代码
? ? } catch(可能出现异常的类型 标识符) {
这种异常出现之后的处理方式
? ? }
try:试一试
try语句块中,是可能会运行失败的代码,try语句是用于对异常进行检测
catch:抓住、捕获
抓住try语句中出现的异常,并且定义异常处理的方式
运行机制:
运行try语句中的代码
如果没有发生任何异常,那么不再运行catch块中的内容
如果发生了catch中声明的异常,那么就会被捕获到这个异常,执行catch块中的内容(try中如果发生了异常,try中,该语句后面的代码都无法执行了,直接跳到catch中)
如果发生了catch中没有声明的异常,那么就无法捕获该异常,该异常的处理就使用jvm的默认处理方式
在一段代码中,可能出现多种异常(虽然一次运行只能出现一个异常,但是出现哪个异常我们是不清楚的),所以要准备多种异常情况的处理机制。
格式:
try {
可能出现异常的代码
} catch (异常类型1 ?异常对象名1) {
异常类型1出现之后的处理办法
} catch (异常类型2 ?异常对象名2) {
异常类型2出现之后的处理办法
}
....
} catch (异常类型n 异常对象名n) {
异常类型n出现之后的处理办法
}
执行流程:
执行try中的内容,如果没有异常,try...catch语句直接结束
如果有异常,那么就在发生异常的代码位置直接跳转到catch块中,try中后面的代码就不再继续运行了
继续匹配各个catch块中的异常类型,从上到下,一旦匹配到某个catch声明的异常类型,就直接执行该catch块的处理方式。处理完成之后,try...catch语句就直接结束了,不会再去匹配后面其他的catch块的异常类型
代码示例:
/**
* 异常捕获的第一种格式的多种异常情况
*/
public class Simple02 {
?
? ?public static void main(String[] args) {
? ? ? ?test();
? }
?
? ?public static void test() {
? ? ? ?Scanner sc = new Scanner(System.in);
? ? ? ?System.out.println("请录入一个除数");
? ? ? ?try {
? ? ? ? ? ?int i = 100 / sc.nextInt();
? ? ? ? ? ?System.out.println(i);
? ? ? ? ? ?int[] arr = {1, 3, 5, 7, 9};
? ? ? ? ? ?System.out.println(arr[5]);
? ? ? }
? ? ? ? ?catch (ArithmeticException a) {
? ? ? ? ? System.out.println("出现数学运算异常");
? ? ? } catch ( ArrayIndexOutOfBoundsException a) {
? ? ? ? ? System.out.println("出现索引越界异常");
? ? ? }
? ? ? ?// catch (ArithmeticException | ArrayIndexOutOfBoundsException a) {
? ? ? ?// ? ? System.out.println(a.getMessage());
? ? ? ?// }
? }
}
?
注意事项:
如果在各个catch块中,出现了子父类的异常类型,那么子类异常的catch块,必须在父类异常catch块的上面,因为从上到下匹配方式,如果父类的catch块在上面,下面的catch块就没有出现的意义了。
在jdk1.7之后,可以对异常类型进行逻辑的或运算,使用|表示,多种异常类型,可以使用相同的处理方式:
catch(异常类型1 | 异常类型2 ?异常对象名称) {
异常类型1和2的共同处理方式a.getMessage()
}
格式:
try {
可能发生异常的代码
}
?catch (可能发生的异常类型 ?异常对象名称) {
当前异常类型的处理方式
} ?finally {
? 一定要执行的代码
}
finally:一定要执行的代码
如果把某句代码放在try中,可能在这句话前面有异常,那么这句话就无法执行;如果把某句代码放在catch中,有可能try中没有异常,就无法执行这句话;如果把某句代码放在try...catch之后,可能有未捕获的异常,那么这句代码也无法执行。
finally:也是一个代码块,在这个代码块中的代码,一定会执行,无论上面描述的哪种情况,都会执行。
作用:一般使用关闭资源
-示例代码:
/**
* 异常捕获的第二种格式
*/
public class Simple03 {
?
? ?public static void main(String[] args) {
? ? ? ?test();
? }
?
? ?public static void test() {
? ? ? ?Scanner sc = new Scanner(System.in);
? ? ? ?System.out.println("请录入一个除数");
? ? ? ?try {
? ? ? ? ? ?int i = 100 / sc.nextInt();
? ? ? ? ? ?System.out.println(i);
? ? ? ? ? ?int[] arr = {1, 3, 5, 7, 9};
? ? ? ? ? ?System.out.println(arr[5]);
? ? ? }
? ? ? ? catch (ArithmeticException ?| ArrayIndexOutOfBoundsException a) {
? ? ? ? ? ? System.out.println(a.getMessage());
? ? ? ? } finally {
? ? ? ? ? ?System.out.println("我一定要执行");
? ? ? }
? }
}
格式:
try {
可能发生异常的代码
} finally {
一定要执行的代码
}
作用:
第三种格式无法捕获和处理异常,一旦发生任何异常,仍然会按照默认的处理方式,一层一层向上抛出,到达jvm,结束虚拟机
无论try中的语句是否发生异常,finally中的代码都一定有执行的机会
如果有两句代码,都需要有执行的机会,不希望第一句的成功与否影响到第二句的执行机会,那么就把这两句代码分别放在try和finally中,就是try中你报你的错,我finally中代码该咋执行咋执行。
代码示例:
import java.util.Scanner;
?
/**
* 异常处理的第三种格式
*/
public class Simple04 {
? ?public static void main(String[] args) {
? ? ? ?try {
? ? ? ? ? ?System.out.println("输入一个除数");
? ? ? ? ? ?System.out.println(1 / new Scanner(System.in).nextInt());
? ? ? } finally {
? ? ? ? ? ?System.out.println("一定有机会执行的代码");
? ? ? }
? }
}
在异常的继承体系中,所有的方法定义在了Throwable这个顶层父类中,子类中几乎没有什么特有方法
Throwable中的构造方法: Throwable():创建一个没有任何参数的异常对象 Throwable(String message):创建一个带有指定消息的异常对象 Throwable(Throwable cause):创建一个有原因异常的异常对象
常用成员方法: getMessage():获取异常的详细信息 toString():获取异常对象的详细信息 printStackTrace():打印异常的调用栈轨迹(有关异常的方法调用路径)
代码示例:
import java.util.Scanner;
?
/**
* 继承体系中的常用方法
*/
public class Simple05 {
? ?public static void main(String[] args) {
? ? ? ?test();
? }
? ?private static void test() {
? ? ? ?try {
? ? ? ? ? ?System.out.println("输入一个除数");
? ? ? ? ? ?System.out.println(1 / new Scanner(System.in).nextInt());
? ? ? } catch (ArithmeticException ae) { ? // 这里可以介绍一下exception异常
? ? ? ? ? ?System.out.println(ae.getMessage());//获取当前异常对象的消息字符串
? ? ? ? ? ?System.out.println(ae.toString());//获取当前异常对象的字符串描述
? ? ? ? ? ?System.out.println("--------------------------");
? ? ? ? ? ?ae.printStackTrace();//获取当前异常对象的方法调用路径
? ? ? }
? }
}
throw:抛出,用于抛出一个异常对象
异常是一个对象,当程序运行到某种情况时,程序员认为这种情况和现实生活不符合,就把当前的对于情况的描述,封装到一个异常对象中,通过throw关键字将异常对象进行抛出。
作用: 创建一个异常对象,使用throw关键字抛出,实现了程序的结束或者跳转
说明:
如果抛出的是运行时异常,在编译阶段就相当于没有异常,可以不处理这个异常
如果抛出的是编译时异常,那么这个异常必须使用异常处理的方式处理,才能编译成功
代码示例
public class Simple06 {
? ?public static void main(String[] args) {
? ? ? ?test();
? }
? ?private static void test() {
? ? ? ?Person person = new Person();
? ? ? ?person.setName("小明");
? ? ? ?try {
? ? ? ? ? ?person.setAge(-20);
? ? ? }catch ( RuntimeException re) {
? ? ? ? ? ?System.out.println(re.getMessage());
? ? ? }
? ? ? ?System.out.println(person);
? }
}
?
public class Person {
? ?private ?String name;
? ?private Integer age;
?
? ?public Person(String name, Integer age) {
? ? ? ?this.name = name;
? ? ? ?this.age = age;
? }
?
? ?public String getName() {
? ? ? ?return name;
? }
?
? ?public Person() {
? }
?
? ?public void setName(String name) {
? ? ? ?this.name = name;
? }
?
? ?public Integer getAge() {
? ? ? ?return age;
? }
?
? ?public void setAge(Integer age) {
? ? ? ?if (age <= 0) {
? ? ? ? ? ?// 年龄小于0,这种情况不符合生活正常情况,就将这种情况封装成异常对象
? ? ? ? ? ?// 如果抛出的是运行时异常,在编译阶段就相当于没有异常,可以不处理这个异常
? ? ? ? ? ?// RuntimeException re = new RuntimeException();
? ? ? ? ? ?// throw re;//将异常对象
? ? ? ? ? throw new RuntimeException("年龄不能为负值");
?
? ? ? }
? ? ? ?this.age = age;
? }
? ?@Override
? ?public String toString() {
? ? ? ?return "Person{" +
? ? ? ? ? ? ? ?"name='" + name + '\'' +
? ? ? ? ? ? ? ?", age=" + age +
? ? ? ? ? ? ? ?'}';
? }
}
throws:抛出,用于声明一个异常类型
在某个方法中,有一些编译时异常,没有给出处理的方案,没有捕获这个异常,没有处理这个异常,就说明这个方法是一个有问题的方法。为了让调用者在调用时,可以考虑到处理这个异常,所必须在当前方法的声明上,声明这个异常。
声明格式:
修饰符 返回值类型 方法名称(参数列表) throws 异常类型1, 异常类型2,... {
? 可能出现异常的代码
}
注意事项:
如果抛出的是一个运行时异常,那么就相当于没有抛出异常,这种异常也不需要在方法上声明;声明了一个运行时异常,也相当于没有做任何声明
如果抛出的是一个编译时异常,那么就必须进行声明或者捕获;如果声明了一个编译时异常,将来调用这个方法时,也相当于有一个声明的异常。
在声明异常的时候,尽量声明<小>的异常、尽量声明<少>的异常
一个方法暴露太多异常,别人不敢调用你了
代码示例:
?
public class Simple07 {
? ?public static void main(String[] args) {
? ? ? ?test();
? }
? ?private static void test() {
? ? ? ?Person person = new Person();
? ? ? ?person.setName("小明");
? ? ? ?try {
? ? ? ? ? ?person.setAge(-20);
? ? ? }catch ( Exception re) {
? ? ? ? ? ?System.out.println(re.getMessage());
? ? ? }
? ? ? ?System.out.println(person);
? }
}
?
public class Person {
? ?private ?String name;
? ?private Integer age;
?
? ?public Person(String name, Integer age) {
? ? ? ?this.name = name;
? ? ? ?this.age = age;
? }
?
? ?public String getName() {
? ? ? ?return name;
? }
?
? ?public Person() {
? }
?
? ?public void setName(String name) {
? ? ? ?this.name = name;
? }
?
? ?public Integer getAge() {
? ? ? ?return age;
? }
?
? ?public void setAge(Integer age) throws Exception {
? ? ? ?if (age <= 0) {
? ? ? ? ? ?// 年龄小于0,这种情况不符合生活正常情况,就将这种情况封装成异常对象
? ? ? ? ? ?// 创建编译时 异常
? ? ? ? ? throw new Exception("年龄不能为负值");
?
? ? ? }
? ? ? ?this.age = age;
? }
?
? ?@Override
? ?public String toString() {
? ? ? ?return "Person{" +
? ? ? ? ? ? ? ?"name='" + name + '\'' +
? ? ? ? ? ? ? ?", age=" + age +
? ? ? ? ? ? ? ?'}';
? }
}
?
throw是对异常对象的抛出,throws是对异常类型的声明
throw是对异常对象实实在在的抛出,一旦使用了throw关键字,就一定有一个异常对象出现;
throws是对可能出现的异常类型的声明,即使声明了异常类型,在该方法中,也可能不出现任何异常。
throw后面只能跟一个异常对象;
throws可以跟很多个异常类型
public class Simple08 {
? ?public static void main(String[] args) {
? ? ? ?test1();
? ? ? ?test2();
? ?try {
? ? ? ? test3();
? ? } catch (ExceptionDemo2 e) {
? ? ? ? System.out.println(e.getMessage());
? ? }
? }
? ?private static void test1() ?throws ?ExceptionDemo1 {
? ? ? ?String str = null;
? ? ? ?System.out.println(str.length());
? }
? ?private static void test2() ?throws ExceptionDemo2 {
? ? ? ?String str = null;
? ? ? ?System.out.println(str.length());
? }
? ?private static void test3() {
? ? ? ?throw new ExceptionDemo2("异常");
? }
}
?
class ExceptionDemo1 extends ?RuntimeException {
?
}
class ExceptionDemo2 extends ?Exception {
? ?public ExceptionDemo2(String message) {
? ? ? ?super(message);
? }
?
? ?public ExceptionDemo2() {
? }
}