一、什么是异常?
- 异常是在程序中导致程序中断运行的一种指令流
- 异常的产生:
- 发生了异常(JVM根据异常的情况,创建了一个异常的对象,包含了异常信息);
- main未自动处理,自动将异常抛给了main的调用者JVM;
- JVM对异常信息进行了响应(将异常信息显示到控制台,并进行中断程序处理)
- 现有如下代码块:
public static void main(String[] args) {
int a = 10;
int b = 0;
System.out.println("123456789");
System.out.println(a/b);
}
那运行结果是什么?
显然,代码出现异常了。
既然异常出现了,我们该怎么解决它,怎么处理它才是最好的办法?
如果要想对异常进行处理,则必须采用标准的处理格式,处理格式语法如下(本文使用是 try+catch进行异常处理):
try{
//有可能发生异常的代码块
}catch(异常类型1 对象名1){
//异常处理操作
}catch(异常类型2 对象名2){
//异常处理操作
}...
finally{
//异常的统一出口
}
那么 try+catch 处理异常的流程是怎样的呢?
流程如下:
- 一旦产生异常,则系统会自动产生一个异常类的实例化对象。
- 那么,此时如果异常发生在try语句,则会自动找到匹配的catch语句执行,如果没有在try语句中,则会将异常抛出.
- 所有的catch根据方法的参数匹配异常类的实例化对象,如果匹配成功,则表示由此catch进行处理。
二、异常的体系结构
- 异常指的是Exception , Exception类, 在Java中存在一个父类Throwable(可能的抛出) Throwable存在两个子类:
- Error:表示的是错误,是JVM发出的错误操作,只能尽量避免,无法用代码处理。
- Exception:一般表示所有程序中的错误,所以一般在程序中将进行try…catch的处理。
从上面的图中可以看出,异常可以分为两个大类: - 受检异常:当系统检测到代码段可能出问题时,会自动飘红提示程序员处理异常(将异常抛出或做其他处理)
- 非受检异常(RuntimeException):当代码段可能会出现异常时,系统不会自动检测,只是在程序运行是进行中断程序运行处理,并将异常输出在控制台上。
三、异常的处理
异常的捕获:
Java提供了try(尝试)、catch(捕捉)、finally(最终)这三个关键字来处理异常。在处理各种异常时,需要用到对应的异常类,指的是由程序抛出的对象所属的类。
示例代码如下:
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.println("请输入一个数字:");
int m = input.nextInt();
System.out.println("请在输入一个数字:");
int n = input.nextInt();
try {
System.out.println(m / n);
System.out.println("处理完毕!");
}catch (ArithmeticException e){
System.out.println("除数不能为0");
}
}
可以看出,在异常捕捉的过程中要进行两个判断,第一是try程序块是否有异常产生,第二是产生的异常是否和catch()括号内想要捕捉的异常相同。
那么,如果出现的异常不止一个时,我们又该如何?事实上我们可以在一个 try 语句后跟上多个异常处理 catch 语句,来处理多种不同类型的异常,这就是多异常处理。
- 示例代码一:
public class Demo01 {
/**
* 处理多异常格式--01
* @param args
*/
public static void main(String[] args) {
try {
Scanner input = new Scanner(System.in);
System.out.println("请输入一个数字:");
int m = input.nextInt();
System.out.println("请在输入一个数字:");
int n = input.nextInt();
System.out.println(m / n);
System.out.println("处理完毕!");
}catch (ArithmeticException e){
System.out.println("除数不能为0");
}catch (InputMismatchException e){
System.out.println("输入的数据格式必须为数字!");
}
}
}
这样,我们就能够针对不同的异常对其进行处理啦~
除此之外,其实我们还有其他多种的格式处理多异常:
- 示例代码二:
public class Demo02 {
/**
* java处理多异常--02(合并处理,了解即可)
* @param args
*/
public static void main(String[] args) {
try {
Scanner input = new Scanner(System.in);
System.out.println("请输入一个数字:");
int m = input.nextInt();
System.out.println("请在输入一个数字:");
int n = input.nextInt();
System.out.println(m / n);
System.out.println("处理完毕!");
}catch (ArithmeticException|InputMismatchException e){
System.out.println("输入有误,请检查!");
}
}
}
通过Java的逻辑表达式 “或”("|")将两个异常做统一的处理。
- 示例代码三:
public class Demo03 {
/**
* java处理多异常--03 常用
* @param args
*/
public static void main(String[] args) {
try {
Scanner input = new Scanner(System.in);
System.out.println("请输入一个数字:");
int m = input.nextInt();
System.out.println("请在输入一个数字:");
int n = input.nextInt();
System.out.println(m / n);
System.out.println("处理完毕!");
}catch (RuntimeException e){
System.out.println("输入有误,请检查!");
}
}
可以看到,这样操作等于是把所有的非受检异常做了统一的处理。
- 示例代码四:
public static void main(String[] args) {
try{
int a = 10;
int b = 0;
System.out.println(a/b);
}catch (Exception e){
System.out.println("出现了异常,退出!");
}
}
上面的这种写法更加直接了当了哈,直接一个catch()将所有的异常全部统一处理,这样做虽然节省了时间和精力,但是它的针对性不强,无法针对某个异常来做相应的处理。(非必要的话不推荐)
四、Finally关键字
在进行异常的处理之后,在异常的处理格式中还有一个finally语句,那么此语句将作为异常的统一出口,不管是否产生了异常,最终都要执行此段代码。
- 示例代码:
public static void main(String[] args) {
try{
int a = 10;
int b = 0;
System.out.println(a/b);
}catch (Exception e){
System.out.println("出现了异常,退出!");
}finally {
System.out.println("123456");
}
}
除此之外熬,finally关键字还有其他特性——在return准备返回值的时候执行。话不多说,上代码:
- 示例代码一:
public class testdemo {
public static void main(String[] args) {
Person p = demo();
System.out.println(p.age);
}
public static Person demo(){
Person p = new Person();
try{
p.age = 18;
return p;
}catch (Exception e){
return null;
}finally {
p.age = 28;
}
}
static class Person{
int age;
}
}
那思考一下,这种情况下,我们控制台输出的内容是什么?
输出28,没错。那为什么呢?
这个得用堆和栈的内存图来讲解:
我们在创建对象的时候,在堆内存中开创了一段内存空间,并给了一个内存地址,假如是0x123,当我们给age赋值的时候,实际上是将这个值存在了堆内存的0x123这个地址的内存空间中,而栈内存中的p拿到的只是0x123这个地址值。当执行到 return p 的时候,return需要进行一个备份,而return备份的是“=”右边的0x123这个内存地址。在return进行备份的时候,finally执行了,将age的值变成了28。那我们再去打印这个age的值的时候,实际上打印的是0x123这个内存地址的值,就是28.
还有一种情况:
- 示例代码二:
public static void main(String[] args) {
int m = demo();
System.out.println(m);
}
public static int demo(){
int a = 10;
try{
return a;
}catch (Exception e){
return 0;
}finally {
a = 20;
}
}
static class Person{
int age;
}
那这种情况又会输出什么呢?
可能有的小伙伴就会说,这个和上面不是一样吗,finally在return准备的时候执行了,肯定输出20啊!
那你就错了~~实际上输出的值还是10。
为什么?上图!
我们在定义a = 10的时候,实际上是在栈内存里面开辟了一段内存空间 a = 10,那下边走到return的时候,return进行备份了,那这个备份实际上也是备份“=”右边的值,也就是10,将它备份到一边。这时候,finally还是执行了,但是它执行改变的栈内存里面的值(还是10),将10变为了20;当我们打印age的时候,其实打印的是return已经备份好在旁边的那个“10”。
这就是引用数据类型(前者)和基本数据类型(后者)的区别所在,一个是在堆内存开辟空间,一个是在栈内存开辟空间。
好了,上面说到,无论什么情况,finally代码都会执行,但是以下两种情况除外:
- 硬件设备导致程序停止,如:电脑断电、关机、内存资源消耗完了等;
- 通过指令控制程序退出JVM,如System.exit();
这个指令有两个参数,分别是0和1:
0表示正常退出,程序正常执行结束退出;
1表示非正常退出,就是说无论程序正在执行与否,都退出。
- 示例代码:
public static void main(String[] args) {
try{
int a = 10;
int b = 0;
System.out.println(a/b);
}catch (Exception e){
System.out.println("出现了异常,退出!");
System.exit(0);//0表示正常退出
}finally {
System.out.println("123456");
}
}
上面可以看出,我们的finally代码块并没有执行,这就是在finally执行前,将整个程序终止运行的方法。
好啦,暂时先分享到这里!