你写的代码编译时是否经常报错?来看看这篇文章帮你解决大部分问题。(Java中的异常)

简介: 异常简单理解就是不正常,Java中的异常就是程序在执行过程中出现不正常的行为称之为异常。

异常的概念与体系结构


概念

异常简单理解就是不正常,Java中的异常就是程序在执行过程中出现不正常的行为称之为异常


常见的异常

1. 算数异常(常见的是除数为0)

System.out.println(10/0);
//执行结果:
Exception in thread "main" java.lang.ArithmeticException: / by zero


2.数组越界异常

int[] arr = {1,2,3};
        System.out.println(arr[10]);
//执行结果:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 10

   

3. 空指针异常

int[] arr = null;
 System.out.println(arr.length);
//执行结果:
Exception in thread "main" java.lang.NullPointerException


从上述例子可以看出:不同的异常对应的异常描述不同,也就是每个异常都有对应的类来进行描述。


异常的体系结构

异常的种类非常的多,为了易于区分和管理,Java内部维护了一个异常的体系结构。见下图:

微信图片_20221028194813.jpg



对上面图进行说明:


1.Throwable:是异常的顶层类,其派生出两个子类:Error和Exception


2. Error:指的是Java虚拟机无法解决的问题,此类问题比较严重,例如:JVM内部错误,资源耗尽等,典型代表:StackOverflowError和OutOfMemoryError,该类异常一但发生,就无法解决,就像人得了癌症。


3.Exception:该类异常产生后可由程序员进行分析及处理,使得程序继续运行,就比如我们人感冒咳嗽一样。


异常的分类

因为异常发生的时机是不一样的,我们根据此将异常分为:编译时异常,运行时异常。


1. 编译时的异常


它指的是在程序编译期间发生的异常,也可称之为受检查异常(Checked Exception)


2. 运行时的异常


它指的是在程序执行期间发生的异常,也称之为非受检查异常(Unchecked Exception)


RuntimeException以及其子类对应的异常都称为运行时异常,例如:NullPointerException等


注意:编译时出现的语法错误不能称为异常,比如将某个单词拼写错误,此时编译过程中就会出错,这是“编译期”出错,而运行时异常指的是,编译已经完成得到class文件,再由JVM执行过程中出现的错误。


异常的处理

防御式编程  

1.LBYL:Look Before You Leap :指的是在对代码进行操作之前做充分的检查,即:事前防御  


例:

boolean ret = false;
      ret = 登录游戏();
      if(!ret){
          处理登录游戏错误;
          return;
      }
      ret = 开始匹配();
      if(!ret){
          处理匹配错误;
          return;
      }
      ret = 游戏确认();
      if(!ret){
          处理游戏确认错误;
          return;
      }
        ......
这种处理方式的缺陷:正确的流程和处理错误的流程混在一起了,整体比较混乱,不易阅读
2.EAFP:It's Easier to Ask Forgiveness than Permission.指的是先进行操作,遇到了问题在处理,即:事后认错型
  try{
            登录游戏();
            开始匹配();
            游戏确认();
            ......
        }catch(登录游戏异常){
            处理登录游戏异常;
        }catch(开始匹配异常){
            处理开始匹配异常;
        }catch(游戏确认异常){
            处理游戏确认异常;
        }


优势:正常和错误流程分开,代码清晰,易于阅读理解


异常处理的5个主要关键字:throw  try  catch  final  throws


异常的抛出

在写程序时,如果程序出现错误,就要将错误的信息告诉给调用者,在Java中,借助throw关键字,抛出一个指定的异常对象,将错误信息告知给调用者,具体语法如下:

throw new XXXException("异常产生的原因");

举一个数组传参的例子进行说明:

public static int getElement(int[] array,int index){
        if(null==array){
            throw new NullPointerException("传递数组为null");
        }
        if(index<0||index>=array.length){
            throw new ArrayIndexOutOfBoundsException("传递的数组下标越界");
        }
        return array[index];
    }
    public static void main(String[] args) {
        int[] array = {1,2,3};
        getElement(array,3);
    }


   运行结果:

 Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 传递的数组下标越界


注意事项:


1.throw必须写在方法体内部


2. 抛出的对象必须是Exception或者Exception的子类对象


3. 如果抛出的是RuntimeException或者它的子类,可以不用处理,JVM会帮我们处理


4. 如果抛出的是编译时异常,用户必须处理,否则无法通过编译


5. 异常一旦抛出,其后的代码就不会执行


异常的捕获

异常的捕获也就是异常的具体处理方式,主要有两种:用throws进行异常声明以及try-catch捕获处理。


异常声明(使用关键字throws)

当程序抛出编译时异常时而且用户不想处理该异常,就可以借助throws将异常抛给方法的调用者来处理。


语法格式:

修饰符 返回值类型 方法名(参数列表) throws 异常类型1,异常类型2...{
}


注意事项:


1. throws必须跟在方法的参数列表之后


2. 声明的异常必须是Exception或者其子类


3. 方法内部如果抛出多个异常,throws之后跟多个异常类型,之间用逗号隔开,如果抛出多个异常类型具有父子关系,则直接声明父类即可


4.调用抛出异常的方法时,调用者必须对该异常进行处理,或者继续使用throws抛出


try-catch捕获处理

throws对异常并没有处理,而是交给调用者处理,如果要对异常真正进行处理,就需要用try-catch


语法格式:

try{
    //这里为可能出现异常的代码
 }catch(要捕获的异常类型 e){
    //当抛出异常与catch捕获异常类型相同时,异常就会被捕获到
     //对异常进行处理完后,会跳出try-catch,继续执行后序代码
 }[catch(异常类型 e){
    //对异常处理
}finally{
    //此处代码一定会被执行
 }]
 //后序代码,异常被捕获处理了,后序代码就一定会执行
 //异常没有捕获到,后序代码就不会执行


注意:[  ]中表示可选项,可以添加,也可以不添加


关于异常的处理方式:


1.异常的种类非常多,我们要根据不同的场景环境来决定


2. 对于更严重的问题(例如栈相关场景),应该让程序直接崩溃,防止造成更严重的后果


3.对于不太严重的问题,可以记录错误日志,并通过监控报警及时通知程序员


4. 对于可能会恢复的问题(网络相关场景),可尝试进行重试


注意事项:


1. try块内抛出异常位置之后的代码将不会被执行


2. 如果抛出异常类型与catch内的异常类型不匹配,即异常不会被成功捕获,也不会被处理,继续往外抛,直到JVM收到后中断程序---异常是按照类型来捕获的


3. try中可能会抛出多种类型异常,则必须用多个catch来捕获多种类型


4. 如果异常之间具有父子关系,一定是子类异常的catch在前,父类异常的catch在后,否则语法错误


由于Exception是所有异常类的父类,所以可以用这个类型表示捕捉所有异常


关键字finally  

在写代码时,有些特定的代码无论程序是否发生异常都需要执行,比如程序中的打开资源,网络连接,数据库连接,IO等,在程序正常或者异常退出时,必须要对资源进行回收,另外,因为异常会引发程序的跳转,可能导致有些语句执行不到,finally就是用来解决这一问题的。


语法格式:

try{
       //可能发生异常的代码 
    }catch(异常类型 e){
        //对捕获的异常进行处理
    }finally{
      //这里的代码无论异常是否被捕获到,都会执行  
    }
    //如果没有异常或者异常被捕获处理了,这里后序的代码也执行

举数组的例子进行说明:

public static void main(String[] args) {
        try{
            int[] array = {1,2,3};
            array[10] = 10;
            array[0] = 10;
        }catch(ArrayIndexOutOfBoundsException e){
            System.out.println(e);
        }finally{
            System.out.println("这里代码一定执行");
        }
        System.out.println("如果没有异常,或者异常被捕获处理,这里代码也将执行");
    }

执行结果:

java.lang.ArrayIndexOutOfBoundsException: 10

这里代码一定执行

如果没有异常,或者异常被捕获处理,这里代码也将执行


这里存在了一个问题:既然finally和try-catch-finally后的代码都会执行,为什么还要有finally的?看完下面的代码,这个问题那就会迎刃而解。

public static int getData(){
        Scanner sc = null;
        try{
            sc = new Scanner(System.in);
            int data = sc.nextInt();
            return data;
        }catch(InputMismatchException e){
           e.printStackTrace();
        }finally{
            System.out.println("finally中的代码");
        }
        System.out.println("try-catch-finally之后的代码");
        if(null!=sc){
            sc.close();
        }
        return 0;
    }
    public static void main(String[] args) {
        int data = getData();
        System.out.println(data);
    }
    输入:10
运行结果:
finally中的代码
10

上述程序,正常输入,return data后,方法就结束了,try-catch-finally后的代码根本没有执行,即输入流没有释放,造成资源泄露。


注意:finally中的代码一定会执行的,一般在finally中进行一些资源清理的扫尾工作。


如果finally中也存在return语句,则执行finally中的return语句,就不会执行try中的return语句


异常的处理流程

1. 程序先执行try中的代码


2. 如果try中的代码出现异常,就会结束try中的代码,与catch中的异常类型是否匹配


3. 如果找到匹配的异常类型,就会执行catch中的代码


4.如果没有找到匹配的类型,就会将异常向上传递到上层调用者


5. 无论是否找到匹配的异常类型,finally中的代码都会被执行到(在该方法结束之前执行)


6.如果上层调用者也没有处理异常,就会继续向上传递,一直到main方法也没有合适的代码处理异常,就会交给JVM来进行处理,此时程序就会异常终止


相关文章
|
5天前
|
Java
在 Java 中捕获和处理自定义异常的代码示例
本文提供了一个 Java 代码示例,展示了如何捕获和处理自定义异常。通过创建自定义异常类并使用 try-catch 语句,可以更灵活地处理程序中的错误情况。
|
19天前
|
XML 安全 Java
Java反射机制:解锁代码的无限可能
Java 反射(Reflection)是Java 的特征之一,它允许程序在运行时动态地访问和操作类的信息,包括类的属性、方法和构造函数。 反射机制能够使程序具备更大的灵活性和扩展性
33 5
Java反射机制:解锁代码的无限可能
|
15天前
|
jenkins Java 测试技术
如何使用 Jenkins 自动发布 Java 代码,通过一个电商公司后端服务的实际案例详细说明
本文介绍了如何使用 Jenkins 自动发布 Java 代码,通过一个电商公司后端服务的实际案例,详细说明了从 Jenkins 安装配置到自动构建、测试和部署的全流程。文中还提供了一个 Jenkinsfile 示例,并分享了实践经验,强调了版本控制、自动化测试等关键点的重要性。
48 3
|
20天前
|
存储 安全 Java
系统安全架构的深度解析与实践:Java代码实现
【11月更文挑战第1天】系统安全架构是保护信息系统免受各种威胁和攻击的关键。作为系统架构师,设计一套完善的系统安全架构不仅需要对各种安全威胁有深入理解,还需要熟练掌握各种安全技术和工具。
59 10
|
16天前
|
分布式计算 Java MaxCompute
ODPS MR节点跑graph连通分量计算代码报错java heap space如何解决
任务启动命令:jar -resources odps-graph-connect-family-2.0-SNAPSHOT.jar -classpath ./odps-graph-connect-family-2.0-SNAPSHOT.jar ConnectFamily 若是设置参数该如何设置
|
15天前
|
Java
Java代码解释++i和i++的五个主要区别
本文介绍了前缀递增(++i)和后缀递增(i++)的区别。两者在独立语句中无差异,但在赋值表达式中,i++ 返回原值,++i 返回新值;在复杂表达式中计算顺序不同;在循环中虽结果相同但使用方式有别。最后通过 `Counter` 类模拟了两者的内部实现原理。
Java代码解释++i和i++的五个主要区别
|
6月前
|
Java
使用Java代码打印log日志
使用Java代码打印log日志
315 1
|
Java BI API
在Java代码中打日志需要注意什么?
日志是什么?日志是你在代码运行时打印出来的一些数据和记录,是快速排查问题的好帮手,是撕逼和甩锅的利器!
701 0
|
缓存 Java 网络架构
别在 Java 代码里乱打日志了,这才是正确的打日志姿势!
别在 Java 代码里乱打日志了,这才是正确的打日志姿势!
167 0
|
Java BI Apache
在Java代码中打日志需要注意什么?
云栖号资讯:【点击查看更多行业资讯】在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来! 为什么要打日志? 日志是什么?日志是你在代码运行时打印出来的一些数据和记录,是快速排查问题的好帮手! 做一件事情之前,先思考为什么。
在Java代码中打日志需要注意什么?