Java语法之异常

news2024/11/25 13:10:45

1.异常的概念以及体系结构

        1.1 异常的概念

        在生活中人会生病,比如咳嗽流鼻涕,头晕等,程序也一样,比如:数据格式不匹配,网络不通畅,内存报警等.在Java中,我们把程序执行的不正常行为称为异常.

        比如:

        1. 算数异常

System.out.println(10 / 0);

// 执行结果

Exception in thread "main" java.lang.ArithmeticException: / by zero

        2.数组越界异常 

nt[] arr = {1, 2, 3}; System.out.println(arr[100]);

// 执行结果

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 100

        3.空指针异常

 int[] arr = null; System.out.println(arr.length);

// 执行结果

Exception in thread "main" java.lang.NullPointerException

        从以上例子我们可以看出,java中不同类型的异常,都有对应的类来进行描述,简而言之,异常其实就是类 

        1.2 异常的体系结构

        为了对异常进行一个比较好的分类,Java内部维护了一个异常的体系结构:

由图可知:

1. Throwable是所有异常的父类,然后由它派生出俩个子类: Error 和 Exception

2.  Error: java虚拟机无法解决的严重错误, 比如: JVM的内部错误,资源耗尽(栈溢出:StackOverflowError和OutOfMemoryError)一旦发生,没有任何东西能阻止.如:人的死亡

3.  Exception: 异常产生之后程序员可以通过代码处理,使程序继续执行,比如:普通的感冒,发烧.

        1.3 异常的分类

        1. 编译时异常

        在编译时期发生的异常(写在编译器上就会冒红(非语法错误)),也成为受查异常(Checked Exception)

        比如之前写的clone()方法

         编译时出现的语法性错误,不能称之为异常。例如将 System.out.println 拼写错了, 写成了system.out.println. 此时编译过程中就会出错, 这是 "编译期" 出错。而运行时指的是程序已经编译通过得到class 文件了, 再由 JVM 执行过程中出现的错误

         2. 运行时异常

        在程序执行期间发生的异常,也成为非受查异常(Unchecked Exception)

        比如算数异常

       RunTimeException以及其子类对应的异常,都称为运行时异常。比如:NullPointerException、ArrayIndexOutOfBoundsException、ArithmeticException...

具体想知道运行谁是运行时异常的子类可以自行去java官方文档查看

       

2.异常的处理

        2.1 防御式编程

        错误在代码中是客观存在的. 因此我们要让程序出现问题的时候及时通知程序猿. 主要的方式

                1. 事前防御型: LBYL: Look Before You Leap. 在操作之前就做充分的检查.

boolean ret = false;
ret = 登陆游戏();
if (!ret) {
处理登陆游戏错误;
return;
        } ret = 开始匹配();
if (!ret) {
处理匹配错误;
return;
        } ret = 游戏确认();
if (!ret) {
处理游戏确认错误;
return;
        } ret = 选择英雄();
if (!ret) {
处理选择英雄错误;
return;
}

        缺点: 代码比较混乱

                2. 事后认错型: It's Easier to Ask Forgiveness than Permission. "事后获取原谅比事前获取许可更容易". 也就是先操作, 遇到问题再处理. (有点先斩后奏的味道了)
 

try {
登陆游戏();
开始匹配();
游戏确认();
选择英雄();
载入游戏画面();
...
        } catch (登陆游戏异常) {
处理登陆游戏异常;
} catch (开始匹配异常) {
处理开始匹配异常;
} catch (游戏确认异常) {
处理游戏确认异常;
} catch (选择英雄异常) {
处理选择英雄异常;
} catch (载入游戏画面异常) {
处理载入游戏画面异常;
}
        ......
        throw new XXXException("异常产生的原因");
public static int getElement(int[] array, int index){
    if(null == array){
        throw new NullPointerException("传递的数组为null");

                优势: 正常流程和错误流程是分开的,代码更加清晰.

        在Java中,异常处理主要用的关键字: throw,try,catch,final,throws. 接下来我们要具体介绍这几个关键字.

        2.2 异常的抛出

        抛出异常的方式有很多:

        1. 手动触发

        2. 某段程序触发

       我们主要讲1, 手动触发异常,我们要使用一个关键字: throw , 抛出一个指定的异常对象,将错误信息告知给调用者(一般抛出的是自定义异常)

        语法格式:

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

        比如这数组指向为空的异常.就是我们手动跑出来的

        注:

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

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

        3. 如果抛出的是编译时异常,用户必须处理,不然用户无法通过编译.

        4. 异常一旦抛出,后面的代码就不能执行了

         2.3 异常的捕获

                我们捕获异常一般有俩种方法:  throws 和 try-catch捕获处理

                1. 异常申明throws

                  当用户在面对一个编译时(受查异常)的时候选择不处理,就可以借助throws来把异常抛给方法的调用者来处理.告诉方法的调用者,调用这个方法,就会抛出一个XXX异常.当前方法不处理异常,提醒方法的调用者处理异常.

                   语法格式:

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

 放在参数列表的后面

        比如上面的clone().

        我们再看看Object里面的clone()方法

因为所有的类都默认继承Object类,又因为Object里面抛出了CloneNotSupportedException,所以继承它的类重写它的clone方法都要throws这个异常(如果不处理).

        注:

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

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

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

public class Config {
    File file;
    // public void OpenConfig(String filename) throws IOException,FileNotFoundException{
// FileNotFoundException 继承自 IOException
    public void OpenConfig(String filename) throws IOException{
        if(filename.endsWith(".ini")){
            throw new IOException("文件不是.ini文件");
        } if(
                filename.equals("config.ini")){
            throw new FileNotFoundException("配置文件名字不对");
        } // 打开文件
    }
    public void readConfig(){
    }
}

                2. 使用try-catch来进行捕获并且处理

                上面的throws并没有对异常进行处理,如果真的要由调用者来处理的话就要使用try-catch.

                语法格式

                try{

                        此处是有可能出现异常的代码

                }catch(要捕获的类型1 e){

                        如果try抛出的异常和catch捕获的异常类型一致,或者是try的父类,就会被捕获到,然后对异常进行正确处理,处理完之后,跳出try-catch,继续执行后续代码

                }catch(要捕获的类型2 e){

                        同上

                }finally{

                这里的代码一定会执行到(一般用来关闭资源)

                }

                后续代码,当异常捕获到,异常被处理了,这里的代码一定会执行,但是捕获到了,但是捕获的类型不对,这里的代码就不会被执行.

        注意: try中的代码可能会抛出异常,也可能不会

        我们来看一个例子:

   public static void main(String[] args) {
        System.out.println("before......");
        try {
            System.out.println(10/0);
            System.out.println("11234");//这里的代码不能打印
        }catch (ArithmeticException e) {//这里面的参数就是你要捕获的异常(要匹配),捕获到了才会执行catch当中的内容
           //这个玩意是使用其他工具来打印的,不是用System.out.println
            e.printStackTrace();
            System.out.println("我来处理ArithmeticException异常了");

        }catch (Exception e) {//我们可以通过catch捕获多个异常,但是同一时刻只能抛出一个异常
            System.out.println("Exception异常了");//这个相当于充当了殿后的角色
        }
        System.out.println("after......");//这个代码不try catch就是jvm来处理的

    }

运行结果:

注意:

1. try里面异常代码后面的正常代码是不会被执行的

2. try里面捕获了才会执行catch续代码,catch里面的异常和你try里面捕获的异常要匹配(异常是按照类型来捕获的),如果抛出异常类型与catch时异常类型不匹配,即异常不会被成功捕获,也就不会被处理,就会层层往上抛直到main没处理就会交给JVM.JVM收到后中断程序,这个和异常不进行(throws,try-catch)处理的结果一样

这里有张我自己总结的图:

3. e.printStackTrace(),打印的是这一块信息

4. 一般我们习惯把父类异常写在子类异常之后当作兜底,比如ArithmeticExceptionn继承于Exception,我们就把后者写在最后一个catch里面.(如果反着写就会报错)

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

异常的种类有很多, 我们要根据不同的业务场景来决定.

对于比较严重的问题(例如和算钱相关的场景), 应该让程序直接崩溃, 防止造成更严重的后果对于不太严重的问题(大多数场景), 可以记录错误日志e.printStackTrace(), 并通过监控报警程序及时通知程序猿对于可能会恢复的问题(和网络相关的场景), 可以尝试进行重试.

在我们当前的代码中采取的是经过简化的第二种方式. 我们记录的错误日志是出现异常的方法调用信息, 能很快速的让我们找到出现异常的位置. 以后在实际工作中我们会采取更完备的方式来记录异常信息.

        3. 关于finally

        一般,程序员在写代码的时候,有些代码不管程序发布发生异常都要执行,比如:程序种打开的资源: 网络连接,数据库连接,IO流等,不管在申明情况下都要对资源进行回收. 另外,因为异常会引发程序的跳转,可能导致有些语句执行不到,finally就是用来解决这个问题的。

语法格式:

try{

// 可能会发生异常的代码

}catch(异常类型 e){

// 对捕获到的异常进行处理

}finally{

// 此处的语句无论是否发生异常,都会被执行到

} / / 如果没有抛出异常,或者异常被捕获处理了,这里的代码也会执行

比如文件操作:

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class TryCatchFinallyExample {
    public static void main(String[] args) {
        BufferedReader reader = null;
        try {
            // 打开文件并创建BufferedReader对象
            reader = new BufferedReader(new FileReader("example.txt"));

            // 逐行读取文件内容并打印
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            // 处理可能的IO异常
            System.err.println("发生IO异常: " + e.getMessage());
        } finally {
            // 确保资源被关闭
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    System.err.println("关闭文件时发生异常: " + e.getMessage());
                }
            }
        }
    }
}

        这里面: reader.close(); 就是在回收资源.

        2.4 常考面试题

                1. throw和throws的区别

                throw是当你需要在代码中手动抛出一个异常时,可以使用 throw.手动抛出一个异常

                throws是把这个异常往上层调用抛(不进行处理),用try-catch才进行处理.主要用于声明一个方法可能抛出的异常类型。(一般面向是受查异常)

                2.finally中的语句一定会执行吗?

                正常情况下:try-catch-finally,无论能否捕获/类型匹不匹配,都会执行finally

                特殊情况: 如果在try或者catch里面调用了System.exit(0),程序就会立即终止,不会执行finally.再如:在try-catch里面代码进入了死循环或者阻塞Thread.sleep(很长时间),都不会执行finally.

再如虚拟机崩溃,JVM被强制关闭(操作系统,硬件外部干预,如直接kill杀死进程),finally也不执行.

        2.5 异常的处理流程

        关于调用栈

        方法之间是存在相互调用关系的, 这种调用关系我们可以用 "调用栈" 来描述. 在 JVM 中有一块内存空间称为"虚拟机栈" 专门存储方法之间的调用关系.

这个东西之前我们就提到过:e.printStackTrace();可以看见异常代码的调用栈.如果本方法中没有合适的处理异常的方式, 就会沿着调用栈向上传递,如果向上一直传递都没有合适的方法处理异常, 最终就会交给 JVM 处理, 程序就会异常终止(和我们最开始未使用 try catch 时是一样的).层层向上直到mian为止还没有处理就给jvm直接终止这个程序.

        异常的处理流程小总结:

        1. 程序先执行try里面的代码

        2. 如果try种的代码出现异常就会结束try的代码,看和catch里面的异常代码是否匹配

        3. 如果找到匹配的异常类型,就会执行catch中的代码(否则就把异常传递给上层调用者,直到处理(正常运行)或者传递到main()还没处理,就交给JVM(直接终止程序))

        4. 无论是否找到匹配的异常类型,finally都会被执行

3.自定义异常

        虽然Java中提供了很多异常的类,但在实际开发中,我们需要面对不同的业务场景产生的异常,就需要自己定义一个异常类.

        我们来观察一下,java中自带的异常怎么写的

        1. 该类继承自Exception类/RuntimeException

        2. 在构造方法里面调用父类的构造方法

比如:我们来自己写一个用户登录功能

我们手动抛出的是一个受查异常需要通过throws来向上抛或者try-chatch进行处理

整体代码:


 class LogIn {
    private String userName = "admin";
    private String password = "123456";
    public static void loginInfo(String userName, String password) throws UserNameException {
        if (!userName.equals(userName)) {
        throw new UserNameException("用户名错误");
        }
        if (!password.equals(password)) {
            try {
                throw new PasswordException("密码错误");
            } catch (PasswordException e) {
                e.printStackTrace();
            }
        } System.out.println("登陆成功");
    }
    public static void main(String[] args) throws UserNameException {
        loginInfo("admin", "123456");
    }
}
class UserNameException extends Exception {
    public UserNameException(String message) {
        super(message);
    }
}
class PasswordException extends Exception {
    public PasswordException(String message) {
        super(message);
    }
}

如果继承的是RuntimeException


 class LogIn {
    private String userName = "admin";
    private String password = "123456";
    public static void loginInfo(String userName, String password) {
        if (!userName.equals(userName)) {
        throw new UserNameException("用户名错误");
        }
        if (!password.equals(password)) {

            throw new PasswordException("密码错误");
        }
         
    }
    public static void main(String[] args) {
        loginInfo("admin", "123456");
    }
}
class UserNameException extends RuntimeException {
    public UserNameException(String message) {
        super(message);
    }
}
class PasswordException extends RuntimeException {
    public PasswordException(String message) {
        super(message);
    }
}

注意:

自定义异常通常会继承自 Exception 或者 RuntimeException

继承自 Exception 的异常默认是受查异常(必须要进行处理)

继承自 RuntimeException 的异常默认是非受查异常(可以不处理,直接交给jvm)

4. 一些注意的点 

        

class Test2 {
    public static void a () throws ArithmeticException{
        int b = 1/0;
        System.out.println("这个是处理完异常之后才会调用出来的代码");
    }

    public static void main(String[] args) {
        try {
            a();
        }catch (ArithmeticException e){
            e.printStackTrace();

        }

        System.out.println("之后的代码");

    }
}

运行结果:

throws用在方法参数列表后面,注意的是,你如果调用该方法就必须对它进行处理,要么你把它继续往上抛,要么用try-catch来处理该异常.但是你如果都抛到了main方法里面,还想继续抛就直接给JVM处理,直接终止程序了,此时如果想打印在main里面调用该方法后面的代码就必须try-catch对该异常进行处理.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2193412.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

python导出requirements.txt的几种方法

整理了python导出requirements.txt的几种方法和流程 Condapippipreqs 最近又碰到整理requirements.txt的问题,有好几种命令可以做到这一点,但是之前整理的时候会输出一堆乱七八糟的包,这次挨个试了所有方法,特此记录。我的平台是a…

【本地缓存】Java 中的 4 种本地缓存

目录 1、手写一个简单的本地缓存1.1、封装缓存实体类1.2、创建缓存工具类1.3、测试 2、Guava Cache2.1、Guava Cache 简介2.2、入门案例2.2.1、引入 POM 依赖2.2.2、创建 LoadingCache 缓存 2.3、Guava Cache 的优劣势和适用场景 3、Caffeine3.1、Caffeine 简介3.2、对比 Guava…

elasticsearch ES DBA常用语句

一、 查看集群状态 curl -uelastic 连接串:端口/_cluster/health?pretty 集群健康有三种状态:green,yellow,red green:所有主要分片、复制分片都可用yellow:所有主要分片可用,但不是所有复制分片都可用red:不是所有…

基于SSM的学生信息管理系统【附源码】

​基于SSM的学生信息管理系统(源码L文说明文档) 目录 4 系统设计 4.1界面设计原则 4.2功能结构设计 4.3数据库设计 4.3.1数据库概念设计 4.3.2 数据库物理设计 第5章 系统实现 5.1管理员功能实现 5.1.1班级和课程关…

基于企业现状定制化的数字化转型路径和战略性架构规划

如何从企业现状出发规划数字化转型 随着技术的迅猛发展,全球企业都在加速推进数字化转型,以增强市场竞争力并提升运营效率。数字化转型并不是一个统一的模板,它要求企业结合自身的业务现状、行业环境和技术基础,制定个性化的转型…

通信工程学习:什么是B/S浏览器服务器模式

B/S:浏览器服务器模式 B/S(Browser/Server,浏览器/服务器)模式,又称B/S结构,是Web兴起后的一种网络结构模式。在这种模式中,Web浏览器是客户端最主要的应用软件,系统功能实现的核心部…

分享一个基于.net的学生信息管理系统 C#高校教务管理系统(源码、调试、LW、开题、PPT)

💕💕作者:计算机源码社 💕💕个人简介:本人 八年开发经验,擅长Java、Python、PHP、.NET、Node.js、Android、微信小程序、爬虫、大数据、机器学习等,大家有这一块的问题可以一起交流&…

计算机毕业设计 智慧物业服务系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍:✌从事软件开发10年之余,专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ 🍅文末获取源码联系🍅 👇🏻 精…

初学Vue(2)

文章目录 监视属性 watch深度监视computed 和 watch 之间的区别 绑定样式(class style)条件渲染列表渲染基本列表key的原理列表过滤列表排序收集表单中的数据 v-model过滤器(Vue3已移除) 监视属性 watch 当被监视的属性变化时&am…

使用frp将树莓派穿透到外网

引言 frp官网 最近买了一块树莓派 zero 2w,想要它可以进行远程访问,所以想到了frp这个方案进行穿透,后期会使用树莓派搭建音乐服务器,本人手机内存有点小,xxxx云音乐太占空间,有兴趣的话可以关注后续。 …

在 window 系统下安装 Ubuntu (虚拟机)

文章目录 零、Ubuntu 和 Vmware workstation 资源一、下载 Ubuntu二、下载 Vmware Workstation Pro三、安装 Vmware Workstation Pro四、创建虚拟机五、配置 Ubuntu 零、Ubuntu 和 Vmware workstation 资源 如果觉得自己下载 Ubuntu 和 Vmware workstation 麻烦,也…

如何在 MySQL 中实现数据压缩

如何在 MySQL 中实现数据压缩 在 MySQL 数据库中,数据压缩可以帮助节省存储空间和提高数据传输效率。本篇文章我就一起来看看关于MySQL数据压缩的相关内容。 一、为什么需要数据压缩 随着数据量的不断增长,数据库的存储空间需求也在不断增加。数据压缩…

【Blender Python】1.概述和基础使用

概述 众所周知,Blender是一款开源免费的3D建模软件(当然不限于3D建模)。在Blender中,可以使用其内置的Python解释器执行Python代码,用于程序化的生成网格以及其他内容。你可以基于此创建Blender插件。 这个系列就是快…

gets和puts

今天我们来学习一组新的函数 gets和puts,它们分别对应的是scanf和printf,但在功能和其它方面有着一些差异 1.gets函数 1.char*gets(char*str); 函数功能:简单来说就像上面的格式一样,给他一个地址(送快递总是要留一个…

Omron/TCP 通信过程

1. 首先 TCP 三次握手 2. 客户端向服务器申请节点地址 客户端向服务器发送一个包含Client Node Address字段的数据包 申请节点地址。由于客户端申请的时候还没有节点地址,因此该字段被置为0x00000000。 3. 服务器向客户端确认收到申请 服务器向客户端发送一个确认…

SQL第12课挑战题

1. 返回customers表中的顾客名称(cust_name)和Orders表中的相关订单号(order_num),并按顾客名称再按订单号对结果进行排序。实际上是尝试两次,一次使用简单的等联结语法,一次使用inner join. 2. 让上一题变得更有用一些…

【算法篇】回溯算法类(2)(笔记)

目录 一、LeetCode 题目 1. 子集II 2. 递增子序列 3. 全排列 4. 全排列 II 5. 重新安排行程 6. N皇后 7. 解数独 二、题目思路整理 一、LeetCode 题目 1. 子集II https://leetcode.cn/problems/subsets-ii/description/https://leetcode.cn/problems/subsets-ii/des…

Spring Cloud Netflix Eureka 注册中心讲解和案例示范

在微服务架构中,服务的发现和注册是至关重要的一环。Netflix Eureka 是一个在云端设计的服务注册与发现系统。它允许各个微服务将自身注册到注册中心,并在需要时发现其他服务,从而实现客户端负载均衡、服务容错以及动态扩展。本文将深入分析 …

【Vue】vue-admin-template项目搭建

准备 node环境 node:v16.12.0npm:8.1.0 vue-element-admin下载 官网:https://panjiachen.github.io/vue-element-admin-site/guide/ 我这边下载的是4.4.0版本的,使用其他版本可能会因为所需要的node和npm版本过低或过高导致异常…