初识Java 13-3 异常

news2025/4/20 8:12:25

目录

try-with-resources语句

一些细节

新特性:try-with-resources中的实际变量

异常匹配

其他可选方式

检查型异常的一些观点

链式异常的使用

异常的使用指南

小结


本笔记参考自: 《On Java 中文版》


try-with-resources语句

        层层叠叠的异常很容易让人看花眼,要确保每一条可能存在的故障路径无疑是一项巨大的挑战。在之前提到的InputFile.java就是一个反面教材。

        可以改进InputFile:①将文件相关的操作(打开、读取和关闭)集中在构造器中,或是②使用Stream进行操作。

【例子:优化InputFile

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.stream.Stream;

public class InputFile2 {
    private String fname;

    public InputFile2(String fname) {
        this.fname = fname;
    }

    public Stream<String> getLines()
            throws IOException {
        return Files.lines(Paths.get(fname));
    }

    public static void main(String[] args)
            throws IOException {
        new InputFile2("InputFile2.java").getLines()
                .skip(14)
                .limit(1)
                .forEach(System.out::println);
    }
}

        程序执行的结果是:

        在上述代码中,getLines()只需要负责打开文件并创建流。

        但实际上,这种问题并没有这么好回避。总是会有对象出乎我们的意料,这些对象往往需要在特定的时刻进行清理(比如走出某个作用域的时候)。

【例子:麻烦的异常处理】

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class MessyExceptions {
    public static void main(String[] args) {
        InputStream in = null;
        try {
            in = new FileInputStream(new File("MessyExceptions.java"));
            int contents = in.read();
            // 对内容进行操作
        } catch (IOException e) {
            // 处理错误
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    // close()可能报错,需要处理它
                }
            }
        }
    }
}

        当我们终于进入了finally块,却发现自己可能还需要继续处理更多的异常。这就会让事情变得过于复杂。

        为了简化这种重复的工作,Java 7引入了try-with-resources语法:

【例子:try-with-resources的使用例】

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class TryWithResources {
    public static void main(String[] args) {
        try (
                InputStream in = new FileInputStream(
                        new File("TryWithResources.java"));
        ) {
            int contents = in.read();
            // 进行各种操作
        } catch (IOException e) {
            // 处理错误
        }
    }
}

        上述的try语法和之前有所不同,出现了一个()

括号中的内容叫做资源说明头,其中in的作用域是整个try块(包括下面的catch子句)。

        更重要的是,在try-with-resources定义子句(即括号内)创建的对象必须实现java.lang.AutoCloseable接口,这个接口只有一个方法 —— close()。现在无论如何退出try块,都会执行操作in.close(),这就缩减了原本复杂的代码。

【例子:try-with-resources的使用例2】

import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.stream.Stream;

public class StreamsAreAutoCloseable {
    public static void main(String[] args)
            throws IOException {
        try ( // 资源头可以包含多个定义,不同定义之间用分号隔开
                Stream<String> in = Files.lines(
                        Paths.get("StreamsAreAutoCloseable.java"));
                PrintWriter outfile = new PrintWriter("Results.txt");
        ) {
            in.skip(4)
                    .limit(1)
                    .map(String::toLowerCase)
                    .forEach(System.out::println);
        }
    }
}

        在资源头中定义的每个对象,它们都会在try块的末尾调用对应的close()

        上述程序中,try块并没有对应的catch子句进行异常处理。这是因为IOException会直接通过main()传递出去,这就使得这一异常不需要在try块的末尾进行捕捉了。

         顺便一提,Java 5实现的Closeable类后来也继承了AutoCloseable类。因此任何支持Closeable的对象也可以配合try-with-resources进行使用。

一些细节

        可以创建自己的AutoCloseable类,来研究try-with-resources的底层机制:

class Reporter implements AutoCloseable {
    String name = getClass().getSimpleName();

    Reporter() {
        System.out.println("创建: " + name);
    }

    @Override
    public void close() {
        System.out.println("关闭: " + name);
    }
}

class First extends Reporter {
}

class Second extends Reporter {
}

public class AutoCloseableDetails {
    public static void main(String[] args) {
        try (
                First f = new First();
                Second s = new Second();
        ) {
        }
    }
}

        程序执行的结果是:

        在退出try块时,调用了两个对象的close()方法。由输出可以发现,会以和创建顺序相反的顺序关闭它们。这么做是考虑到不同的对象之间可能存在着依赖关系。

        若某个类没有实现AutoCloseable接口,就会引发报错:

        另外,让我们再来看看构造器报错的情况:

【例子:构造器报错(在资源说明头中抛出异常)】

class CE extends Exception {
}

class SecondExcept extends Reporter {
    SecondExcept() throws CE {
        super();
        throw new CE();
    }
}

public class ConstructorException {
    public static void main(String[] args) {
        try (
                First f = new First();
                SecondExcept s = new SecondExcept();
                Second s2 = new Second();
        ) {
            System.out.println("在try块的内部");
        } catch (CE e) {
            System.out.println("捕获异常:" + e);
        }
    }
}

        程序执行的结果是:

        因为语句SecondExcept s = new SecondExcept();会抛出异常,所以编译器会强制要求我们提供一个catch子句来捕获它。这侧面反映了资源说明头实际上是被try块包围的。

        仔细观察可以发现,SecondExceptclose()方法并没有被调用。这是因为构造已经失败了,我们无法假定在其上进行的任何操作是安全的

------

【例子:在try块中抛出异常】

class Third extends Reporter {
}

public class BodyException {
    public static void main(String[] args) {
        try (
                First f = new First();
                Second s2 = new Second();
        ) {
            System.out.println("在try块中");
            Third t = new Third();
            new SecondExcept(); // 会抛出异常
            System.out.println("try块结束");
        } catch (CE e) {
            System.out.println("捕获异常:" + e);
        }
    }
}

        程序执行的结果是:

        注意:上述程序中的Third对象永远不会得到清理,因为它不是在资源说明头中进行创建的。

        实际上,若是依赖于某个集成开发环境将代码重写为try-with-resources的形式,它们有可能只会保护所遇到的第一个对象,而忽略其他的对象。

------

【例子:close()抛出异常】

class CloseException extends Exception {
}

class Reporter2 implements AutoCloseable {
    String name = getClass().getSimpleName();

    Reporter2() {
        System.out.println("创建:" + name);
    }

    @Override
    public void close() throws CloseException {
        System.out.println("关闭:" + name);
    }
}

class Closer extends Reporter2 {
    @Override
    public void close() throws CloseException {
        super.close();
        throw new CloseException();
    }
}

public class CloseExceptions {
    public static void main(String[] args) {
        try (
                First f = new First();
                Closer c = new Closer();
                Second s = new Second();
        ) {
            System.out.println("在try块中");
        } catch (CloseException e) {
            System.out.println("捕获异常:" + e);
        }
    }
}

        程序执行的结果是:

        一个好的习惯是将错误处理代码放置在catch子句中。

        在这里,三个对象按照顺序创建,并且按照相反的顺序关闭,即使Closer.close()会抛出异常。


新特性:try-with-resources中的实际变量

        最初,try-with-resources中的所有被管理的变量都需要被定义在资源说明头中。但从JDK 9开始,这些被管理的变量也可以被定义在try之前,只要它们是最终变量(或是实际上的最终变量)。

【例子:对比新旧语法】

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class EffectivelyFinalTWR {
    static void old() {
        try (
                InputStream r1 = new FileInputStream(
                        new File("InputFile2.java"));
                InputStream r2 = new FileInputStream(
                        new File("EffectivelyFinalTWR.java"))
        ) {
            r1.read();
            r2.read();
        } catch (IOException e) {
            // 处理异常
        }
    }

    static void jdk9() throws IOException {
        // 最终变量
        final InputStream r1 = new FileInputStream(
                new File("InputFile2.java"));
        // 实际上的最终变量
        final InputStream r2 = new FileInputStream(
                new File("EffectivelyFinalTWR.java"));
        try (r1; r2) { // 将变量放入资源说明头
            r1.read();
            r2.read();
        } catch (IOException e) {
            System.out.println("在jdk9内部捕获异常:" + e);
        }

        // 此时r1和r2都被关闭
        // 但r1和r2还存在于作用域中,访问其中的任何一个都会引发异常
        r1.read();
        r2.read();
    }

    public static void main(String[] args) {
        old();
        try {
            jdk9();
        } catch (IOException e) {
            System.out.println("捕获异常:" + e);
        }
    }
}

        程序执行的结果是:

        jdk9()会把异常传递出来。这个特性无法捕获异常,所以它看起来不怎么可信。

异常匹配

        当一个异常被抛出的时候,异常处理系统会按照处理程序的编写顺序寻找能够匹配异常的那个。当找到第一个匹配的处理程序时,系统会认为异常得到了处理,就不会进一步进行搜索了。

        匹配异常不会要求完全匹配,子类的对象可以匹配其基类的处理程序:

【例子:异常匹配】

class Annoyance extends Exception {
}

class Sneeze extends Annoyance {
}

public class Human {
    public static void main(String[] args) {
        // 捕获精确的类型:
        try {
            throw new Sneeze();
        } catch (Sneeze s) {
            System.out.println("捕获异常,来自Sneeze");
        } catch (Annoyance a) {
            System.out.println("捕获异常,来自Annoyance");
        }

        // 捕获基类类型:
        try {
            throw new Sneeze();
        } catch (Annoyance a) {
            System.out.println("捕获异常,来自Annoyance");
        }
    }
}

        程序执行的结果是:

        上述代码中,catch (Annoyance a)将捕获Annoyance或者任何派生自它的类

        另外,若将基类异常的catch子句放在前面,子类的异常就永远无法被触发:

其他可选方式

        异常处理允许我们的程序放弃正常语句序列的执行。但若需要处理每个调用可能产生的错误,就会显得过于繁琐。程序员不会这么做,但这会导致错误被忽略。注意,便于程序员处理错误是异常处理的主要动机之一

        异常处理有一个重要准则:除非知道怎么处理,否则不要捕获异常

        另外,通过允许一个处理程序应付多个出错点,异常往往也能减少错误处理的代码量。但检查型异常会使得这种情况变得更加复杂,因为它可能会强迫我们在无法处理错误的地方添加catch子句,这就会造成“吞食有害”

try {
    // ...(一些有用的操作)
} catch(ObligatoryException e) {} // 强制性的异常处理

程序员往往只做最简单的事情,这就会造成在无意间“吞食”了异常。编译通过,此时除非进行复查并修正代码,否则异常就相当于丢失了。

    一种处理方式是在处理程序中打印栈轨迹信息。尽管这种做法可以追踪异常,但这也表明我们没有真正理解如何在这个位置处理这个异常。

||| 格言:所有的模型都是错误的,但有些是有用的。

        在评价Java的检查型异常时,应该牢记:这种异常应该能够引导程序员以更好的方式处理错误,并且不会增加太多的代码量

检查型异常的一些观点

        检查型异常是Java的一种尝试,实际上之后的编程语言也没有采用这种做法。

        检查型异常或许能够在小型的程序中展示出其的妙用。但随着程序规模的增大,这种情况就会有所变化。有些语言可能不会适合大型项目,但却适合与小型项目。在增大的项目中,增多的检查型异常是难以控制的。

    甚至有一种结论:在大型软件项目中,要求异常说明带来的结果是开发效率的降低,和代码质量几乎没有提高。


链式异常的使用

        检查型异常的麻烦是需要解决的。这里有一种简单的解决方案,将一个检查型异常传递给RuntimeException构造器,就可以将这个异常包裹在RuntimeException中:

try {
    // ... 有用的处理
} catch(IDontKnowWhatToDoWithThisCheckedException e) {
    throw new RuntimeException(e); // 将e包裹进去,变成非检查型异常
}

由于异常链的存在,我们不会丢失任何来自原始异常的信息。

        这使得我们有了选择:忽略这个异常,使其传递到更上层的上下文中;或者使用getCause()捕获和处理特定的异常。

【例子:异常链的使用】

import java.io.FileNotFoundException;
import java.io.IOException;

class WrapCheckedException {
    void throwRuntimeException(int type) {
        try {
            switch (type) {
                case 0:
                    throw new FileNotFoundException();
                case 1:
                    throw new IOException();
                case 2:
                    throw new RuntimeException("不断移动的RuntimeException异常");
                default:
                    return;
            }
        } catch (IOException | RuntimeException e) {
            throw new RuntimeException(e); // 将检查型异常处理成非检查型异常
        }
    }
}

class SomeOtherException extends Exception {
}

public class TurnOffChecking {
    public static void main(String[] args) {
        WrapCheckedException wce = new WrapCheckedException();

        // 这里可以不使用try块
        // 通过直接调用throwRuntumeException(),可以让RuntimeException离开这个方法
        wce.throwRuntimeException(3);

        // 也可以选择捕获该异常:
        for (int i = 0; i < 4; i++) {
            try {
                if (i < 3)
                    wce.throwRuntimeException(i);
                else
                    throw new SomeOtherException();
            } catch (SomeOtherException e) {
                System.out.println(
                        "捕获异常SomeOtherException: " + e);
            } catch (RuntimeException re) {
                try {
                    throw re.getCause();
                } catch (FileNotFoundException e) {
                    System.out.println("捕获异常FileNotFoundException: " + e);
                } catch (IOException e) {
                    System.out.println("捕获异常IOException: " + e);
                } catch (Throwable e) {
                    System.out.println("捕获异常Throwable: " + e);
                }
            }
        }
    }
}

        程序执行的结果是:

        通过将异常捕获并包入RuntimeException,可以将检查型异常转换成运行时异常的cause

        另外,当我们准备捕捉异常的时候,我们通过会将代码放入一个try块中。此时我们仍然可以捕获任何我们想要的异常,首先捕捉我们明确知道的那些异常。当我们捕获RuntimeException时,通过抛出getCause()的结果,就可以提取出原始的异常了。

异常的使用指南

        下面是一些异常使用的指南原则:

  • 尽可能使用try-with-resources。
  • 在恰当的层次处理问题(除非知道如何处理,否则不要捕获异常)。
  • 可以使用异常修复问题,并重新调用引发异常的方法。
  • 可以选择做好补救措施后继续,不再重写尝试引发异常的方法。
  • 可以借助异常处理的过程计算戳某个结果,以替代方法本该生成的值。
  • 可以在当前上下文完成能够完成的事,再将相同/不同的异常重新抛出。
  • 使用异常来终止程序。
  • 使用异常来简化问题(注意,若异常模式使问题变复杂了,用起来会非常麻烦)。
  • 使用异常,使我们的库和程序更加安全(方便调试,提高程序稳健性)。

小结

        异常使得我们可以集中精力在一个地方处理程序原本需要解决的问题,并在另一个地方处理来自代码的错误。异常的一个重要功能在于其的“报告”,Java坚持所有错误以异常的形式报告,这是一个优点。

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

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

相关文章

消除springboot+thymeleaf时页面的红色波浪线告警

setting->Editor->Inspections取消掉Thymeleaf的√ 效果&#xff1a;

Zabbix登录页面出现数据库访问异常

问题概述 这是我遇到过最无语的一套zabbix服务器部署&#xff0c;也不知道是否有心要刁难我…搞定一波又来一波&#xff0c;每次都不重样的。这次是客户自己不知道倒腾什么东西&#xff0c;竟然是将zabbix密码给改了的。他的意思是项目完毕&#xff0c;公司规定必须要将密码进…

记录:R语言生成热图(非相关性)

今天解决了一个困扰了我很久的问题&#xff0c;就是如何绘制不添加相关性的热图。一般绘制热图是使用corrplot包画相关性图&#xff0c;但是这样有一个前提&#xff0c;就是输入的数据集必须进行相关性分析。那么如果我不需要进行相关性分析&#xff0c;而是直接绘制能够反应数…

微信小程序 movable-view 控制长按才触发拖动 轻轻滑动页面正常滚动效果

今天写 movable-areamovable-view遇到了个头疼的问题 那就是 movable-view 监听了用户拖拽自己 但 我们小程序 上下滚动页面靠的也是拖拽 也就是说 如果放在这里 用户拖动 movable-view部分 就会永远触发不了滚动 那么 我们先可以 加一个 bindlongpress"longpressHandler…

网工内推 | 技术支持工程师,厂商公司,HCIA即可,有带薪年假

01 华为终端有限公司 招聘岗位&#xff1a;初级技术支持 职责描述&#xff1a; 1、通过远程方式处理华为用户在产品使用过程中各种售后问题&#xff1b; 2、收集并整理消费者声音&#xff0c;提供服务持续优化建议&#xff1b; 3、对服务中发现的热点、难点问题及其他有可能造…

火伞云Web应用防火墙的特点与优势

在前文中&#xff0c;我们已经介绍了Web应用防火墙&#xff08;WAF&#xff09;的基本原理和重要性。接下来&#xff0c;我们将深入探讨火伞云Web应用防火墙的特点与优势&#xff0c;了解它如何为企业提供更为完善和专业的网络安全保障。 一、强大的防御能力 火伞云Web应用防火…

【数据库——MySQL(实战项目1)】(1)图书借阅系统——数据库结构设计

目录 1. 简述2. 功能3. 数据库结构设计3.1 绘制 E-R 图3.2 创建数据库3.3 创建表3.4 插入表数据 1. 简述 经过前期的学习&#xff0c;我们已经掌握数据库基础操作&#xff0c;因此是时候来做一个实战项目了——图书借阅系统。对于图书借阅系统&#xff0c;相信大家不难想到至少…

天猫用户重复购买预测(速通一)

天猫用户重复购买预测&#xff08;一&#xff09; 赛题理解1、评估指标2、赛题分析 理论知识1.缺失值处理2.不均衡样本3.常见的数据分布 数据探索探查影响复购的各种因素1.对店铺分析2.对用户分析3.对用户性别的分析4.对用户年龄的分析 特征工程1、特征工程介绍特征归一化类别型…

Docker系列--镜像和容器备份与恢复的方法

原文网址&#xff1a;Docker系列--镜像和容器备份与恢复的方法_IT利刃出鞘的博客-CSDN博客 简介 说明 本文介绍备份和恢复Docker镜像与容器的方法。 命令对比 保存与导出 docker save&#xff1a;保存的是镜像&#xff08;image&#xff09;。&#xff08;保存的是分层的…

刷新页面,数据丢失

刷新页面数据丢失原因很多&#xff0c;其中有一种是解析错误&#xff0c;没有解构出来。 报错内容如下(类似这个报错)&#xff1a; 数据结构如下&#xff1a; this.$router.push({name: DetailComparison,query: {rowDetail: rowDetail || null} }) 修改数据结构&#xff1a…

2023C语言暑假作业day7

1选择题 1 以下对C语言函数的有关描述中&#xff0c;正确的有【多选】&#xff08; &#xff09; A: 在C语言中&#xff0c;一个函数一般由两个部分组成&#xff0c;它们是函数首部和函数体 B: 函数的实参和形参可以是相同的名字 C: 在main()中定义的变量都可以在其它被调函数…

python安装(windows64简洁版)

一、Python安装 软件安装四部曲 官网下载&#xff08;注意电脑和版本&#xff09;&#xff1a;python官网. 例如window64下载对应版本 环境变量配置 在命令提示框中(cmd) : 输入 path%path%;C:\Python 按下 Enter。 注意: C:\Python 是Python的安装目录。 验证是否安装成功 编…

Windows下Redis3.0集群搭建

redis版本&#xff1a;Redis-x64-3.0.504 复制相同文件 修改文件夹下redis.windows.conf 文件配置(注意&#xff1a;主有密码&#xff0c;从必须有密码且跟主相同) 修改端口&#xff1a; 主库&#xff1a;端口号6379 从库1&#xff1a;修改端口号为6380 从库2&#xff1a;修…

家政服务小程序,家政维修系统,专业家政软件开发商;家政服务小程序,家政行业软件开发

家政服务小程序&#xff0c;家政维修系统&#xff0c;专业家政软件开发商&#xff1b; 家政服务小程序&#xff0c;家政行业软件开发解决方案&#xff0c;家政软件经验丰富实践&#xff0c;系统高度集成&#xff0c;提供师傅端、用户端、… 家政服务app开发架构有 1、后台管理端…

Java的File文件操作案例汇总

Java的File文件操作案例汇总 案例汇总【1】MultipartFile与File的互转&#xff08;1&#xff09;前言&#xff08;2&#xff09;MultipartFile转File&#xff08;3&#xff09;手动删除&#xff08;4&#xff09;File转MultipartFile 【2】前端上传文件&#xff0c;后端解析出文…

拓扑排序求最长路

P1807 最长路 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 题目要求我们求出第1号到第n号节点之间最长的距离。 我们想到使用拓扑排序来求最长路。 正常来讲&#xff0c;我们应该把1号节点入队列&#xff0c;再出队列&#xff0c;把一号节点能到达的所有的点的入度减一&a…

网线接法aaa

![(https://img-blog.csdnimg.cn/d2901403dbd44feaa8f7be669ddcf2fc.png) 加粗样式 在这里插入图片描述

ZTree自定义不可展开节点的折叠图标

目录 1. 场景2. 实现过程3. ZTree 关键配置代码4. 小结 &#x1f4ac; 1. 场景 需求是自定义去控制某个节点&#xff0c;让它不可以展开&#xff0c;但因为它底下有子节点&#xff0c;所以默认的折叠图标还是&#xff0b;号。 也就是要实现让不可展开的节点前的折叠图标显示-号…

浅谈IT运维-服务请求管理与变更管理的关系与区别

服务请求 用户或用户授权代表发起的服务动作的请求&#xff0c;该服务动作已约定为服务交付的正常部分。服务请求是用户问询的重要类型,也是用户体验的重要组成部分。通常,服务请求包括以下内容: 发起服务动作请求(由服务提供者或与用户一起执行)信息请求资源或服务访问请求反…

Python还有什么赋值?Python赋值语句的多种形式

Python是一种强大且灵活的编程语言&#xff0c;其赋值语句的多样性是其特色之一。除了常见的变量赋值&#xff0c;Python还支持许多其他赋值方式&#xff0c;包括多重赋值、增量赋值以及解包赋值等。在本文中&#xff0c;我们将深入探讨Python中赋值语句的多种形式&#xff0c;…