十五、异常(6)

news2024/9/27 17:31:48

本章概要

  • Try-With-Resources 用法
    • 揭示细节
  • 异常匹配

Try-With-Resources 用法

在考虑所有可能失败的方法时,找出放置所有 try-catch-finally 块的位置变得令人生畏。确保没有任何故障路径,使系统远离不稳定状态,这非常具有挑战性。

InputFile.java 是一个特别棘手的情况,因为文件被打开(伴随所有可能因此产生的异常),然后它在对象的生命周期中保持打开状态。每次调用 getLine()都可能导致异常,而且 dispose()也是这种情况。这个例子只是好在它显示了事情可以混乱到什么地步。它还表明了你应该尽量不要那样设计代码(当然,你经常会遇到这种无法选择的代码设计的情况,因此你仍然必须要理解它)。

InputFile.java 一个更好的实现方式是如果构造函数读取文件并在内部缓冲它 —— 这样,文件的打开,读取和关闭都发生在构造函数中。或者,如果读取和存储文件不切实际,你可以改为生成 Stream。理想情况下,你可以设计成如下的样子:

package base;// exceptions/InputFile2.java

import java.io.*;
import java.nio.file.*;
import java.util.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("D:\\onJava\\myTest\\base\\InputFile2.java").getLines()
                .skip(17)
                .limit(1)
                .forEach(System.out::println);
    }
}

输出为:

在这里插入图片描述

现在,getLines() 全权负责打开文件并创建 Stream。

你不能总是轻易地回避这个问题。有时会有以下问题:

  1. 需要资源清理
  2. 需要在特定的时刻进行资源清理,比如你离开作用域的时候(在通常情况下意味着通过异常进行清理)。

一个常见的例子是 java.io.FileInputStream 。要正确使用它,你必须编写一些棘手的样板代码:

import java.io.*;

public class MessyExceptions {
    public static void main(String[] args) {
        InputStream in = null;
        try {
            in = new FileInputStream(new File("MessyExceptions.java"));
            int contents = in.read();
            // Process contents
        } catch (IOException e) {
            // Handle the error
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    // Handle the close() error
                }
            }
        }
    }
}

当 finally 子句有自己的 try 块时,感觉事情变得过于复杂。

幸运的是,Java 7 引入了 try-with-resources 语法,它可以非常清楚地简化上面的代码:

import java.io.*;

public class TryWithResources {
    public static void main(String[] args) {
        try (
                InputStream in = new FileInputStream(new File("TryWithResources.java"))
        ) {
            int contents = in.read();
            // Process contents
        } catch (IOException e) {
            // Handle the error
        }
    }
}

在 Java 7 之前,try 后面总是跟着一个 {,但是现在可以跟一个带括号的定义 ——这里是我们创建的 FileInputStream 对象。括号内的部分称为资源规范头(resource specification header)。现在 in 在整个 try 块的其余部分都是可用的。更重要的是,无论你如何退出 try 块(正常或通过异常),和以前的 finally 子句等价的代码都会被执行,并且不用编写那些杂乱而棘手的代码。这是一项重要的改进。

它是如何工作的? try-with-resources 定义子句中创建的对象(在括号内)必须实现 java.lang.AutoCloseable 接口,这个接口只有一个方法:close()。当在 Java 7 中引入 AutoCloseable 时,许多接口和类被修改以实现它;查看 Javadocs 中的 AutoCloseable,可以找到所有实现该接口的类列表,其中包括 Stream 对象:

StreamsAreAutoCloseable.java

import java.io.*;
import java.nio.file.*;
import java.util.stream.*;

public class StreamsAreAutoCloseable {
    public static void main(String[] args) throws IOException {
        try (
                Stream<String> in = Files.lines(Paths.get("D:\\onJava\\myTest\\base\\StreamsAreAutoCloseable.java"));
                PrintWriter outfile = new PrintWriter("D:\\onJava\\myTest\\base\\Results.txt"); // [1]
        ) {
            in.skip(5)
                    .limit(1)
                    .map(String::toLowerCase)
                    .forEachOrdered(outfile::println);
        } // [2]
    }
}

Results.txt

import java.nio.file.*;
  • [1] 你在这里可以看到其他的特性:资源规范头中可以包含多个定义,并且通过分号进行分割(最后一个分号是可选的)。规范头中定义的每个对象都会在 try 语句块运行结束之后调用 close() 方法。
  • [2] try-with-resources 里面的 try 语句块可以不包含 catch 或者 finally 语句而独立存在。在这里,IOException 被 main() 方法抛出,所以这里并不需要在 try 后面跟着一个 catch 语句块。

Java 5 中的 Closeable 已经被修改,修改之后的接口继承了 AutoCloseable 接口。所以所有实现了 Closeable 接口的对象,都支持了 try-with-resources 特性。

揭示细节

为了研究 try-with-resources 的基本机制,我们将创建自己的 AutoCloseable 类:

// exceptions/AutoCloseableDetails.java
class Reporter implements AutoCloseable {
    String name = getClass().getSimpleName();

    Reporter() {
        System.out.println("Creating " + name);
    }

    @Override
    public void close() {
        System.out.println("Closing " + 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() 方法,并以与创建顺序相反的顺序关闭它们。顺序很重要,因为在这种情况下,Second 对象可能依赖于 First 对象,因此如果 First 在第 Second 关闭时已经关闭。 Second 的 close() 方法可能会尝试访问 First 中不再可用的某些功能。

假设我们在资源规范头中定义了一个不是 AutoCloseable 的对象

// exceptions/TryAnything.java
// {WillNotCompile}
class Anything {
}

public class TryAnything {
    public static void main(String[] args) {
        try (
                Anything a = new Anything()
        ) {
        }
    }
}

正如我们所希望和期望的那样,Java 不会让我们这样做,并且出现编译时错误。

如果其中一个构造函数抛出异常怎么办?

// exceptions/ConstructorException.java
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("In body");
        } catch (CE e) {
            System.out.println("Caught: " + e);
        }
    }
}

输出为:

在这里插入图片描述

现在资源规范头中定义了 3 个对象,中间的对象抛出异常。因此,编译器强制我们使用 catch 子句来捕获构造函数异常。这意味着资源规范头实际上被 try 块包围。

正如预期的那样,First 创建时没有发生意外,SecondExcept 在创建期间抛出异常。请注意,不会为 SecondExcept 调用 close(),因为如果构造函数失败,则无法假设你可以安全地对该对象执行任何操作,包括关闭它。由于 SecondExcept 的异常,Second 对象实例 s2 不会被创建,因此也不会有清除事件发生。

如果没有构造函数抛出异常,但在 try 的主体中可能抛出异常,那么你将再次被强制要求提供一个catch 子句:

// exceptions/BodyException.java
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("In body");
            Third t = new Third();
            new SecondExcept();
            System.out.println("End of body");
        } catch (CE e) {
            System.out.println("Caught: " + e);
        }
    }
}

输出为:

在这里插入图片描述

请注意,第 3 个对象永远不会被清除。那是因为它不是在资源规范头中创建的,所以它没有被保护。这很重要,因为 Java 在这里没有以警告或错误的形式提供指导,因此像这样的错误很容易漏掉。实际上,如果依赖某些集成开发环境来自动重写代码,以使用 try-with-resources 特性,那么它们(在撰写本文时)通常只会保护它们遇到的第一个对象,而忽略其余的对象。

最后,让我们看一下抛出异常的 close() 方法:

// exceptions/CloseExceptions.java
class CloseException extends Exception {
}

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

    Reporter2() {
        System.out.println("Creating " + name);
    }

    @Override
    public void close() throws CloseException {
        System.out.println("Closing " + 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("In body");
        } catch (CloseException e) {
            System.out.println("Caught: " + e);
        }
    }
}

输出为:

在这里插入图片描述

从技术上讲,我们并没有被迫在这里提供一个 catch 子句;你可以通过 main() throws CloseException 的方式来报告异常。但 catch 子句是放置错误处理代码的典型位置。

请注意,因为所有三个对象都已创建,所以它们都以相反的顺序关闭 - 即使 Closer.close() 抛出异常也是如此。仔细想想,这就是你想要的结果。但如果你必须亲手编写所有的逻辑,或许会丢失一些东西并使得逻辑出错。想想那些程序员没有考虑 Clean up 的所有影响并且出错的代码。因此,如果可以,你应当始终使用 try-with-resources。这个特性有助于生成更简洁,更易于理解的代码。

异常匹配

抛出异常的时候,异常处理系统会按照代码的书写顺序找出“最近”的处理程序。找到匹配的处理程序之后,它就认为异常将得到处理,然后就不再继续查找。

查找的时候并不要求抛出的异常同处理程序所声明的异常完全匹配。派生类的对象也可以匹配其基类的处理程序,就像这样:

// exceptions/Human.java
// Catching exception hierarchies
class Annoyance extends Exception {
}

class Sneeze extends Annoyance {
}

public class Human {
    public static void main(String[] args) {
        // Catch the exact type:
        try {
            throw new Sneeze();
        } catch (Sneeze s) {
            System.out.println("Caught Sneeze");
        } catch (Annoyance a) {
            System.out.println("Caught Annoyance");
        }
        // Catch the base type:
        try {
            throw new Sneeze();
        } catch (Annoyance a) {
            System.out.println("Caught Annoyance");
        }
    }
}

输出为:

在这里插入图片描述

Sneeze 异常会被第一个匹配的 catch 子句捕获,也就是程序里的第一个。然而如果将这个 catch 子句删掉,只留下 Annoyance 的 catch 子句,该程序仍然能运行,因为这次捕获的是 Sneeze 的基类。换句话说,catch(Annoyance a)会捕获 Annoyance 以及所有从它派生的异常。这一点非常有用,因为如果决定在方法里加上更多派生异常的话,只要客户程序员捕获的是基类异常,那么它们的代码就无需更改。

如果把捕获基类的 catch 子句放在最前面,以此想把派生类的异常全给“屏蔽”掉,就像这样:

try {
    throw new Sneeze();
} catch(Annoyance a) {
    // ...
} catch(Sneeze s) {
    // ...
}

此时,编译器会发现 Sneeze 的 catch 子句永远得不到执行,因此它会向你报告错误。

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

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

相关文章

Unity ToLua热更框架使用教程(1)

从本篇开始将为大家讲解ToLua在unity当中的使用教程。 Tolua的框架叫LuaFramework&#xff0c;首先附上下载链接&#xff1a; https://github.com/jarjin/LuaFramework_UGUI_V2 这个地址的是UGUI的。 下载完之后导入项目&#xff0c;首先&#xff0c;我们要先让这个项目跑起…

老卫带你学---Datagrip连接clickhouse

Datagrip连接clickhouse Datagrip是一个DB可视化特别方便的软件&#xff0c;因为一些业务需要采用clickhouse&#xff0c;然而在download相关driver的时候出现各种问题&#xff0c;于是整理一下方案 1.需要下载clickhouse-jdbc的jar包&#xff0c;可以直接在sonatype上去下载…

C# 人像卡通化

效果 项目 代码 using Microsoft.ML.OnnxRuntime; using Microsoft.ML.OnnxRuntime.Tensors; using OpenCvSharp; using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Threading.Tasks; using System.Windows.Forms;nam…

图像分割-Segment Anything实践

一、模型介绍 Segment Anything 模型是一种新的图像分割模型&#xff0c;它可以在不需要大量标注数据的情况下&#xff0c;对图像中的任何物体进行分割。这种方法可以帮助计算机视觉领域的研究人员和开发人员更轻松地训练模型&#xff0c;从而提高计算机视觉应用程序的性能。该…

超前预告 | 云原生?大模型?这届乌镇双态IT大会亮点有点多

石道旁的水面&#xff0c;轻轻泛着微光&#xff0c;几片墨绿缓缓飘下&#xff0c;荡起柔和的波纹&#xff0c;向对岸游去。这儿不似北方秋阳如火的躁动&#xff0c;这儿的秋色是安静的&#xff0c;里便是江南水乡乌镇…… 2023年&#xff0c;第六届双态IT乌镇用户大会将于10月…

不再为文件名大小写烦恼:批量转换,一招搞定

在电脑使用过程中&#xff0c;我们经常需要处理各种文件&#xff0c;有时需要对文件名进行大小写转换以符合特定要求或便于管理。手动修改不仅费时还容易出错&#xff0c;那么有没有一种方法可以批量转换文件名大小写呢&#xff1f;答案是肯定的&#xff0c;下面就为大家介绍如…

DC电源模块在电容滤波器上的设计

BOSHIDA DC电源模块在电容滤波器上的设计 DC电源模块在电容滤波器上的设计是电源管理系统中非常重要的一部分&#xff0c;其目的是为了确保电源输出电压的稳定性和纹波尽可能小。在设计中&#xff0c;需要考虑到电源负载的变化和变压器等电源配件的电磁干扰等因素。下面我们详细…

基于Java的民宿管理系统设计与实现(源码+lw+部署文档+讲解等)(民宿预约、民宿预订、民宿管理、酒店预约通用)

文章目录 前言具体实现截图论文参考详细视频演示代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技…

【数据结构】二叉树--堆排序

目录 一 降序(建小堆) 二 升序 (建大堆) ​三 优化(以升序为例) 四 TOP-K问题 一 降序(建小堆) void Swap(int* x, int* y) {int tmp *x;*x *y;*y tmp; }//降序 建小堆 void AdjustUp(int* a, int child) {int parent (child - 1) / 2;while (child > 0){if (a[chil…

(三)Apache log4net™ 手册 -演示

0、引言 在开始本文之前&#xff0c;推荐您首先阅读 Apache log4net™ 手册中有关 介绍 与 配置 的相关内容。本文将通过实践分别为您演示如何使用 Visual Studio 2022 在 .NET Framework 项目和 .NET 项目下配置并使用 Log4Net。 1、为 .NET Framework 项目配置 Log4Net 1.1…

day26--AJAX(axios使用,http协议(部分),接口文档,form-serialize使用)

目录 AJAX介绍&#xff1a; 什么是AJAX 怎么用AJAX&#xff1f; axios的使用&#xff1a; axios的核心配置&#xff1a; url统一资源定位符&#xff1a; 组成&#xff1a; http协议&#xff1a; 域名&#xff1a; 资源路径&#xff1a; 查询参数&#xff1a; 常用的…

Text embedding 模型总结

文章目录 MTEB榜单8个嵌入任务三种数据集类别 C_METB榜单文本向量表示模型 目前&#xff0c;随着 Langchain LLM模型 的火热&#xff0c;除了层出不穷的大模型外&#xff0c;因为检索的能力会很大程度影响最终的问答效果&#xff0c;文本的嵌入模型也是大家比较关注的。本文主…

Redis之主从复制,哨兵模式,集群

Redis之主从复制&#xff0c;哨兵模式&#xff0c;集群 1、主从复制1.1主从复制概述1.2Redis主从复制作用1.3Redis主从复制流程1.4部署Redis 主从复制 2、哨兵模式2.1哨兵模式原理2.2哨兵模式的作用2.3哨兵模式的结构2.4故障转移机制2.5搭建Redis 哨兵模式 3、Redis集群模式3.1…

在开发APP过程中外包我们经常会遇到哪些问题?我们该如何避免?

虽然选择一个外包公司有很多坑&#xff0c;但是我们有的时候不得不选择一个外包公司&#xff0c;所以选择外包的时候我们要注意一些细节可以有效的区分开外包app产品的可靠性还是至关重要&#xff01;希望我整理的这些对各位有需要的同学有所帮助&#xff01; 首先我们先从公司…

看好你家电视盒的后门!数千个Android电视盒感染了与欺诈相关的危险恶意软件

如果你从Android电视盒获得流媒体修复程序&#xff0c;则你的设备可能会被恶意软件所感染&#xff0c;这些恶意软件能够进行广告欺诈、创建假帐户&#xff0c;并通过悄悄地将你的数据转移到中国的服务器来销售对家庭网络的访问。 根据本周的一份新报告&#xff0c;网络安全公司…

深入理解强化学习——强化学习的目标和数据

分类目录&#xff1a;《深入理解强化学习》总目录 强化学习的目标 在动态环境下&#xff0c;智能体和环境每次进行交互时&#xff0c;环境会产生相应的奖励信号&#xff0c;其往往由实数标量来表示。这个奖励信号一般是诠释当前状态或动作的好坏的及时反馈信号&#xff0c;好比…

射频识别技术课程实验--模拟串口间的通信--基础实验

射频识别技术课程实验–模拟串口间的通信 前期准备 串口调试小助手&#xff1a; 模拟串口工具&#xff1a; Visual Studio 2022&#xff1a; 测试代码&#xff08;c&#xff09;&#xff1a; #include<iostream> #include<Windows.h>using namespace std;int ma…

Redis未授权访问漏洞实验

1 Redis简介 Redis是一个开源的内存数据库管理系统&#xff0c;它被广泛用于缓存、消息队列和实时数据分析等应用场景。Redis支持多种数据结构&#xff0c;包括字符串、列表、集合、有序集合和哈希表&#xff0c;可以通过简单的键值对方式存储和检索数据。由于其高性能和低延迟…

C++day03(动态内存、类中特殊成员函数)

今日任务 1> 思维导图 2> 设计一个Per类&#xff0c;类中包含私有成员:姓名、年龄、指针成员身高、体重&#xff0c;再设计一个Stu类&#xff0c;类中包含私有成员:成绩、Per类对象p1&#xff0c;设计这两个类的构造函数、析构函数和拷贝构造函数。 代码&#xff1a; …

【刷题】只出现一次的数字(三种解法)

【刷题】只出现一次的数字 文章目录 【刷题】只出现一次的数字解法异或运算解法一 : 异或运算解法二:集合类Set集合Map集合 链接: https://www.nowcoder.com/share/jump/2008263481696810321082 https://leetcode.cn/problems/single-number/description/ 题目描述 给定一个整…