初识Java 13-3 异常

目录

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);}@Overridepublic 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);}@Overridepublic void close() throws CloseException {System.out.println("关闭:" + name);}
}class Closer extends Reporter2 {@Overridepublic 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);elsethrow 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.rhkb.cn/news/156526.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

高考600分能上哪些大学 分数线在600以下的大学

大部分学生在高考时最关注一定就是自己的分数最终可以考上哪些大学了&#xff0c;其中600是很多人最关注的一个分数线&#xff0c;那么高于600分的话可以上哪些大学呢&#xff1f;下面就来告诉大家吧。 高考600分能上的大学&#xff1a;湖南大学、西南大学、西安电子科技大学、…

一文理清JVM结构

JVM结构介绍 JVM一共分为三个组成部分: 1 类加载子系统 主要是将class文件加载到内存中的一个系统&#xff0c;其核心组件是类加载器 2 运行时数据区子系统 1 JVM私有部分 1 虚拟机栈 描述的是Java方法执行的内存模型&#xff1a;每个方法在执行的同时都会创建一个栈帧&…

Android绑定式服务

Github:https://github.com/MADMAX110/Odometer 启动式服务对于后台操作很合适&#xff0c;不过需要一个更有交互性的服务。 接下来构建这样一个应用&#xff1a; 1、创建一个绑定式服务的基本版本&#xff0c;名为OdometerService 我们要为它增加一个方法getDistance()&#x…

linux下安装ffmpeg的详细教程、ffmpeg is not installed

1、下载解压 wget http://www.ffmpeg.org/releases/ffmpeg-6.0.tar.gz tar -zxvf ffmpeg-6.0.tar.gz 2、 进入解压后目录,输入如下命令/usr/local/ffmpeg为自己指定的安装目录 cd ffmpeg-6.0 ./configure --prefix/usr/local/ffmpeg make sudo make install 3、配置变量 v…

软件测试工具有什么作用?有哪些好用的测试工具推荐?

软件测试工具是现代软件测试中不可或缺的重要组成部分&#xff0c;指的是一系列在软件开发过程中使用的工具&#xff0c;用于帮助测试人员进行测试活动&#xff0c;提高测试效率&#xff0c;减少测试成本。选择并使用合适的软件测试工具&#xff0c;可提高软件质量和效率。 一…

2023-2024年华为ICT网络赛道模拟题库

2023-2024年网络赛道模拟题库上线啦&#xff0c;全面覆盖网络&#xff0c;安全&#xff0c;vlan考点&#xff0c;都是带有解析 参赛对象及要求&#xff1a; 参赛对象&#xff1a;现有华为ICT学院及未来有意愿成为华为ICT学院的本科及高职院校在校学生。 参赛要求&#xff1a…

解决maven骨架加载慢问题(亲测解决)

1、下载archetype-catalog.xml 网站 &#xff1a; https://repo.maven.apache.org/maven2/ 2、放在这个文件夹下面 3、setting–>build–>Runner : -DarchetypeCataloglocal

stm32 hal库 st7789 1.54寸lcd

文章目录 前言一、软件spi1.cubemx配置2.源码文件 二、硬件spi1.cubemx配置2.源码文件3.小小修改 总结 前言 1.54寸lcd 240*240 一、软件spi 1.cubemx配置 一定要注意把这几个东西上拉。 使用c8 2.源码文件 我使用的是中景园的源码&#xff0c;他本来是是标准库的稍微修改…

rv1126-rknpu-v1.7.3添加opencv库

rv1126所使用的rknn sdk里默认是不带opencv库的&#xff0c;官方所用的例程里也没有使用opencv&#xff0c;但是这样在进行图像处理的时候有点麻烦了&#xff0c;这里有两种办法: 一是先用python将所需要的图片处理好后在转化为bin格式文件&#xff0c;在使用c或c进行读取&…

Jetson Orin NX 开发指南(8): Mavros 的安装与配置

一、前言 由于 Jetson 系列开发板常作为自主无人机的机载电脑&#xff0c;而无人机硬件平台如 PX4 和 ArduPilot 等通过 MAVLink 进行发布无人机状态和位姿等信息&#xff0c;要实现机载电脑与 MAVLink 的通信&#xff0c;必须借助 Mavros 功能包&#xff0c;因此&#xff0c;…

二叉搜索树--查询节点-力扣 700 题

例题细节讲过(二叉搜索树的基础操作-CSDN博客)&#xff0c;下面给出递归实现 public TreeNode searchBST(TreeNode node, int val) {if(node null) {return null;}if(val < node.val) {return searchBST(node.left, val);} else if(node.val < val) {return searchBST(…

新手如何快速上手HTTP爬虫IP?

对于刚接触HTTP爬虫IP的新手来说&#xff0c;可能会感到有些困惑。但是&#xff0c;实际上HTTP爬虫IP并不复杂&#xff0c;只要掌握了基本的操作步骤&#xff0c;就可以轻松使用。本文将为新手们提供一个快速上手HTTP爬虫IP的入门指南&#xff0c;帮助您迅速了解HTTP爬虫IP的基…

Python利用jieba分词提取字符串中的省市区(字符串无规则)

目录 背景库&#xff08;jieba&#xff09;代码拓展结尾 背景 今天的需求就是在一串字符串中提取包含&#xff0c;省、市、区&#xff0c;该字符串不是一个正常的地址;,如下字符串 "安徽省、浙江省、江苏省、上海市,冷运标快首重1kg价格xx元,1.01kg(含)-5kg(不含)续重价…

计算机毕业设计选什么题目好?springboot 航司互售系统

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

日常学习收获之----react的ref和wrappedComponentRef的区别

react获取子组件的方式&#xff0c;有ref和wrappedComponentRef。那这两者有什么区别呢&#xff1f; 区别在于是否用了高阶组件&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#…

【Mybatis源码】IDEA中Mybatis源码环境搭建

一、Mybatis源码源 在github中找到Mybatis源码地址&#xff1a;https://github.com/mybatis/mybatis-3 找到Mybatis git地址 二、IDEA导入Mybatis源码 点击Clone下载Mybatis源码 三、选择Mybatis分支 选择Mybatis分支&#xff0c;这里我选择的是3.4.x分支

ctfshow-ssti

web361 名字就是考点&#xff0c;所以注入点就是name 先测试一下存不存在ssti漏洞 利用os模块&#xff0c;脚本 查看一下子类的集合 ?name{{.__class__.__base__.__subclasses__()}} 看看有没有os模块&#xff0c;查找os 利用这个类&#xff0c;用脚本跑他的位置 import …

github Release 下载加速,绿色合法,遥遥领先

你有没有这样一个困惑&#xff0c;当你寻找了很久终于找到一个解决问题的方案&#xff0c;发现这个工具在 GitHub 上&#xff0c;接下来等待我们的就是遥遥无期的龟速下载。 文章目录 前言下载测试加速下载操作 视频讲解 遥遥领先 前言 GitHub 作为程序员的知识宝库&#xff…

在九天服务器平台上使用自己上传的数据集文件

1.进入到“数据管理”中&#xff0c;点击“新建数据” 2. 创建实例&#xff0c;进入到Jupyter页面 刚才上传的文件在data文件夹中 3. 将data文件夹中上传的数据集放到其他目录&#xff0c;比如这里我就放到了~/目录下 # 要把当前文件夹下的指定文件复制到目的路径的命令&…

Android 项目增加 res配置

main.res.srcDirs "src/main/res_test" build->android->sourceSets