你写的代码编译时是否经常报错?来看看这篇文章帮你解决大部分问题。(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新纪元启航】JDK 22:解锁未命名变量与模式,让代码更简洁,思维更自由!
【9月更文挑战第7天】JDK 22带来的未命名变量与模式匹配的结合,是Java编程语言发展历程中的一个重要里程碑。它不仅简化了代码,提高了开发效率,更重要的是,它激发了我们对Java编程的新思考,让我们有机会以更加自由、更加创造性的方式解决问题。随着Java生态系统的不断演进,我们有理由相信,未来的Java将更加灵活、更加强大,为开发者们提供更加广阔的舞台。让我们携手并进,共同迎接Java新纪元的到来!
29 11
|
3天前
|
并行计算 Java 开发者
探索Java中的Lambda表达式:简化代码,提升效率
Lambda表达式在Java 8中引入,旨在简化集合操作和并行计算。本文将通过浅显易懂的语言,带你了解Lambda表达式的基本概念、语法结构,并通过实例展示如何在Java项目中应用Lambda表达式来优化代码,提高开发效率。我们将一起探讨这一现代编程工具如何改变我们的Java编码方式,并思考它对程序设计哲学的影响。
|
3天前
|
安全 Java 测试技术
掌握Java的并发编程:解锁高效代码的秘密
在Java的世界里,并发编程就像是一场精妙的舞蹈,需要精准的步伐和和谐的节奏。本文将带你走进Java并发的世界,从基础概念到高级技巧,一步步揭示如何编写高效、稳定的并发代码。让我们一起探索线程池的奥秘、同步机制的智慧,以及避免常见陷阱的策略。
|
10天前
|
Java API 开发者
代码小妙招:用Java轻松获取List交集数据
在Java中获取两个 `List`的交集可以通过 `retainAll`方法和Java 8引入的流操作来实现。使用 `retainAll`方法更为直接,但会修改原始 `List`的内容。而使用流则提供了不修改原始 `List`、更为灵活的处理方式。开发者可以根据具体的需求和场景,选择最适合的方法来实现。了解和掌握这些方法,能够帮助开发者在实际开发中更高效地处理集合相关的问题。
10 1
|
11天前
|
开发者 C# 存储
WPF开发者必读:资源字典应用秘籍,轻松实现样式与模板共享,让你的WPF应用更上一层楼!
【8月更文挑战第31天】在WPF开发中,资源字典是一种强大的工具,用于共享样式、模板、图像等资源,提高了应用的可维护性和可扩展性。本文介绍了资源字典的基础知识、创建方法及最佳实践,并通过示例展示了如何在项目中有效利用资源字典,实现资源的重用和动态绑定。
27 0
|
11天前
|
Java 开发者
探索Java中的Lambda表达式:简化你的代码
【8月更文挑战第31天】 在Java 8的发布中,Lambda表达式无疑是最令人兴奋的新特性之一。它不仅为Java开发者提供了一种更加简洁、灵活的编程方式,而且还极大地提高了代码的可读性和开发效率。本文将通过实际代码示例,展示如何利用Lambda表达式优化和重构Java代码,让你的编程之旅更加轻松愉快。
|
11天前
|
Java 开发者
探索Java中的Lambda表达式:简化代码的现代方法
【8月更文挑战第31天】Lambda表达式在Java 8中首次亮相,为Java开发者提供了一种更简洁、灵活的编程方式。它不仅减少了代码量,还提升了代码的可读性和可维护性。本文将通过实际示例,展示Lambda表达式如何简化集合操作和事件处理,同时探讨其对函数式编程范式的支持。
Java基础异常-自定义异常
Java基础异常-自定义异常
Java基础异常-自定义异常
|
Java 编译器
Java中的异常(抛出异常、自定义异常等)
Java中的异常(抛出异常、自定义异常等)
267 1
|
Java 程序员 编译器
Java异常——throw、throws及自定义异常
Java异常——throw、throws及自定义异常
121 0