十五、异常(5)

本章概要

  • 异常限制
  • 构造器

异常限制

当覆盖方法的时候,只能抛出在基类方法的异常说明里列出的那些异常。这个限制很有用,因为这意味着与基类一起工作的代码,也能和导出类一起正常工作(这是面向对象的基本概念),异常也不例外。

下面例子演示了这种(在编译时)施加在异常上面的限制:

// exceptions/StormyInning.java
// Overridden methods can throw only the exceptions
// specified in their base-class versions, or exceptions
// derived from the base-class exceptions
class BaseballException extends Exception {
}class Foul extends BaseballException {
}class Strike extends BaseballException {
}abstract class Inning {Inning() throws BaseballException {}public void event() throws BaseballException {// Doesn't actually have to throw anything}public abstract void atBat() throws Strike, Foul;public void walk() {} // Throws no checked exceptions
}class StormException extends Exception {
}class RainedOut extends StormException {
}class PopFoul extends Foul {
}interface Storm {void event() throws RainedOut;void rainHard() throws RainedOut;
}public class StormyInning extends Inning implements Storm {// OK to add new exceptions for constructors, but you// must deal with the base constructor exceptions:public StormyInning()throws RainedOut, BaseballException {}public StormyInning(String s)throws BaseballException {}// Regular methods must conform to base class://- void walk() throws PopFoul {} //Compile error// Interface CANNOT add exceptions to existing// methods from the base class://- public void event() throws RainedOut {}// If the method doesn't already exist in the// base class, the exception is OK:@Overridepublic void rainHard() throws RainedOut {}// You can choose to not throw any exceptions,// even if the base version does:@Overridepublic void event() {}// Overridden methods can throw inherited exceptions:@Overridepublic void atBat() throws PopFoul {}public static void main(String[] args) {try {StormyInning si = new StormyInning();si.atBat();} catch (PopFoul e) {System.out.println("Pop foul");} catch (RainedOut e) {System.out.println("Rained out");} catch (BaseballException e) {System.out.println("Generic baseball exception");}// Strike not thrown in derived version.try {// What happens if you upcast?Inning i = new StormyInning();i.atBat();// You must catch the exceptions from the// base-class version of the method:} catch (Strike e) {System.out.println("Strike");} catch (Foul e) {System.out.println("Foul");} catch (RainedOut e) {System.out.println("Rained out");} catch (BaseballException e) {System.out.println("Generic baseball exception");}}
}

在 Inning 类中,可以看到构造器和 event() 方法都声明将抛出异常,但实际上没有抛出。这种方式使你能强制用户去捕获可能在覆盖后的 event() 版本中增加的异常,所以它很合理。这对于抽象方法同样成立,比如 atBat()。

接口 Storm 包含了一个在 Inning 中定义的方法 event() 和一个不在 Inning 中定义的方法 rainHard()。这两个方法都抛出新的异常 RainedOut,如果 StormyInning 类在扩展 Inning 类的同时又实现了 Storm 接口,那么 Storm 里的 event() 方法就不能改变在 Inning 中的 event() 方法的异常接口。否则的话,在使用基类的时候就不能判断是否捕获了正确的异常,所以这也很合理。当然,如果接口里定义的方法不是来自于基类,比如 rainHard(),那么此方法抛出什么样的异常都没有问题。

异常限制对构造器不起作用。你会发现 StormyInning 的构造器可以抛出任何异常,而不必理会基类构造器所抛出的异常。然而,因为基类构造器必须以这样或那样的方式被调用(这里默认构造器将自动被调用),派生类构造器的异常说明必须包含基类构造器的异常说明。

派生类构造器不能捕获基类构造器抛出的异常。

StormyInning.walk() 不能通过编译是因为它抛出了一个 Inning.walk() 中没有声明的异常。如果编译器允许这么做的话,就可以编写调用Inning.walk()却不处理任何异常的代码。 但是当使用 Inning派生类的对象时,就会抛出异常,从而导致程序出现问题。通过强制派生类遵守基类方法的异常说明,对象的可替换性得到了保证。

覆盖后的 event() 方法表明,派生类版的方法可以不抛出任何异常,即使基类版的方法抛出了异常。因为这样做不会破坏那些假定基类版的方法会抛出异常的代码。类似的情况出现在 atBat()上,它抛出的异常PopFoul是由基类版atBat()抛出的Foul 异常派生而来。如果你写的代码同 Inning 一起工作,并且调用了 atBat()的话,那么肯定能捕获 Foul 。又因为 PopFoul 是由 Foul派生而来,因此异常处理程序也能捕获 PopFoul

最后一个有趣的地方在 main()。如果处理的刚好是 Stormylnning 对象的话,编译器只要求捕获这个类所抛出的异常。但是如果将它向上转型成基类型,那么编译器就会准确地要求捕获基类的异常。所有这些限制都是为了能产生更为健壮的异常处理代码。

尽管在继承过程中,编译器会对异常说明做强制要求,但异常说明本身并不属于方法类型的一部分,方法类型是由方法的名字与参数的类型组成的。因此,不能基于异常说明来重载方法。此外,一个出现在基类方法的异常说明中的异常,不一定会出现在派生类方法的异常说明里。这点同继承的规则明显不同,在继承中,基类的方法必须出现在派生类里,换句话说,在继承和覆盖的过程中,某个特定方法的“异常说明的接口”不是变大了而是变小了——这恰好和类接口在继承时的情形相反。

构造器

有一点很重要,即你要时刻询问自己“如果异常发生了,所有东西能被正确的清理吗?"尽管大多数情况下是非常安全的,但涉及构造器时,问题就出现了。构造器会把对象设置成安全的初始状态,但还会有别的动作,比如打开一个文件,这样的动作只有在对象使用完毕并且用户调用了特殊的清理方法之后才能得以清理。如果在构造器内抛出了异常,这些清理行为也许就不能正常工作了。这意味着在编写构造器时要格外细心。

你也许会认为使用 finally 就可以解决问题。但问题并非如此简单,因为 finally 会每次都执行清理代码。如果构造器在其执行过程中半途而废,也许该对象的某些部分还没有被成功创建,而这些部分在 finally 子句中却是要被清理的。

在下面的例子中,建立了一个 InputFile 类,它能打开一个文件并且每次读取其中的一行。这里使用了 Java 标准输入/输出库中的 FileReader 和 BufferedReader 类,这些类的基本用法很简单,你应该很容易明白:

import java.io.*;public class InputFile {private BufferedReader in;public InputFile(String fname) throws Exception {try {in = new BufferedReader(new FileReader(fname));// Other code that might throw exceptions} catch (FileNotFoundException e) {System.out.println("Could not open " + fname);// Wasn't open, so don't close itthrow e;} catch (Exception e) {// All other exceptions must close ittry {in.close();} catch (IOException e2) {System.out.println("in.close() unsuccessful");}throw e; // Rethrow} finally {// Don't close it here!!!}}public String getLine() {String s;try {s = in.readLine();} catch (IOException e) {throw new RuntimeException("readLine() failed");}return s;}public void dispose() {try {in.close();System.out.println("dispose() successful");} catch (IOException e2) {throw new RuntimeException("in.close() failed");}}
}

InputFile 的构造器接受字符串作为参数,该字符串表示所要打开的文件名。在 try 块中,会使用此文件名建立 FileReader 对象。FileReader 对象本身用处并不大,但可以用它来建立 BufferedReader 对象。注意,使用 InputFile 的好处之一是把两步操作合而为一。

如果 FileReader 的构造器失败了,将抛出 FileNotFoundException 异常。对于这个异常,并不需要关闭文件,因为这个文件还没有被打开。而任何其他捕获异常的 catch 子句必须关闭文件,因为在它们捕获到异常之时,文件已经打开了(当然,如果还有其他方法能抛出 FileNotFoundException,这个方法就显得有些投机取巧了。这时,通常必须把这些方法分别放到各自的 try 块里),close() 方法也可能会抛出异常,所以尽管它已经在另一个 catch 子句块里了,还是要再用一层 try-catch,这对 Java 编译器而言只不过是多了一对花括号。在本地做完处理之后,异常被重新抛出,对于构造器而言这么做是很合适的,因为你总不希望去误导调用方,让他认为“这个对象已经创建完毕,可以使用了”。

在本例中,由于 finally 会在每次完成构造器之后都执行一遍,因此它实在不该是调用 close() 关闭文件的地方。我们希望文件在 InputFlle 对象的整个生命周期内都处于打开状态。

getLine() 方法会返回表示文件下一行内容的字符串。它调用了能抛出异常的 readLine(),但是这个异常已经在方法内得到处理,因此 getLine() 不会抛出任何异常。在设计异常时有一个问题:应该把异常全部放在这一层处理;还是先处理一部分,然后再向上层抛出相同的(或新的)异常;又或者是不做任何处理直接向上层抛出。如果用法恰当的话,直接向上层抛出的确能简化编程。在这里,getLine() 方法将异常转换为 RuntimeException,表示一个编程错误。

用户在不再需要 InputFile 对象时,就必须调用 dispose() 方法,这将释放 BufferedReader 和/或 FileReader 对象所占用的系统资源(比如文件句柄),在使用完 InputFile 对象之前是不会调用它的。可能你会考虑把上述功能放到 finalize() 里面,但我在 封装 讲过,你不知道 finalize() 会不会被调用(即使能确定它将被调用,也不知道在什么时候调用),这也是 Java 的缺陷:除了内存的清理之外,所有的清理都不会自动发生。所以必须告诉客户端程序员,这是他们的责任。

对于在构造阶段可能会抛出异常,并且要求清理的类,最安全的使用方式是使用嵌套的 try 子句:

// exceptions/Cleanup.java
// Guaranteeing proper cleanup of a resource
public class Cleanup {public static void main(String[] args) {try {InputFile in = new InputFile("D:\\onJava\\myTest\\base\\Cleanup.java");try {String s;int i = 1;while ((s = in.getLine()) != null) {// Perform line-by-line processing here...}} catch (Exception e) {System.out.println("Caught Exception in main");e.printStackTrace(System.out);} finally {in.dispose();}} catch (Exception e) {System.out.println("InputFile construction failed");}}
}

输出为:

在这里插入图片描述

请仔细观察这里的逻辑:对 InputFile 对象的构造在其自己的 try 语句块中有效,如果构造失败,将进入外部的 catch 子句,而 dispose() 方法不会被调用。但是,如果构造成功,我们肯定想确保对象能够被清理,因此在构造之后立即创建了一个新的 try 语句块。执行清理的 finally 与内部的 try 语句块相关联。在这种方式中,finally 子句在构造失败时是不会执行的,而在构造成功时将总是执行。

这种通用的清理惯用法在构造器不抛出任何异常时也应该运用,其基本规则是:在创建需要清理的对象之后,立即进入一个 try-finally 语句块:

// exceptions/CleanupIdiom.java
// Disposable objects must be followed by a try-finally
class NeedsCleanup { // Construction can't failprivate static long counter = 1;private final long id = counter++;public void dispose() {System.out.println("NeedsCleanup " + id + " disposed");}
}class ConstructionException extends Exception {
}class NeedsCleanup2 extends NeedsCleanup {// Construction can fail:NeedsCleanup2() throws ConstructionException {}
}public class CleanupIdiom {public static void main(String[] args) {// [1]:NeedsCleanup nc1 = new NeedsCleanup();try {// ...} finally {nc1.dispose();}// [2]:// If construction cannot fail,// you can group objects:NeedsCleanup nc2 = new NeedsCleanup();NeedsCleanup nc3 = new NeedsCleanup();try {// ...} finally {nc3.dispose(); // Reverse order of constructionnc2.dispose();}// [3]:// If construction can fail you must guard each one:try {NeedsCleanup2 nc4 = new NeedsCleanup2();try {NeedsCleanup2 nc5 = new NeedsCleanup2();try {// ...} finally {nc5.dispose();}} catch (ConstructionException e) { // nc5 const.System.out.println(e);} finally {nc4.dispose();}} catch (ConstructionException e) { // nc4 const.System.out.println(e);}}
}

输出为:

在这里插入图片描述

  • [1] 相当简单,遵循了在可去除对象之后紧跟 try-finally 的原则。如果对象构造不会失败,就不需要任何 catch。
  • [2] 为了构造和清理,可以看到将具有不能失败的构造器的对象分组在一起。
  • [3] 展示了如何处理那些具有可以失败的构造器,且需要清理的对象。为了正确处理这种情况,事情变得很棘手,因为对于每一个构造,都必须包含在其自己的 try-finally 语句块中,并且每一个对象构造必须都跟随一个 try-finally 语句块以确保清理。

本例中异常处理的混乱情形,有力的论证了应该创建不会抛出异常的构造器,尽管这并不总会实现。

注意,如果 dispose() 可以抛出异常,那么你可能需要额外的 try 语句块。基本上,你应该仔细考虑所有的可能性,并确保正确处理每一种情况。

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

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

相关文章

大话机器学习准确率(Accuracy)、精确率(Pecision)、召回率(Recall)以及TP、FP、TN、FN

话说三国时期,乱世出人才,当时刘备让张飞帮忙招兵买马,寻找人才。张飞发公告以后,有10人来面试,这10人分为两类,人才和庸才,各占百分之五十,张飞的主要作用就是从这10人中识别出人才…

UWB PDOA定位原理

以下是笔记总结,内容不完全正确 1,什么是PDOA PDOA &#xff0c;英文全称是Phase-Difference-of-Arrival&#xff0c;信号到达相位差 PDOA定位算法的原理如下&#xff1a; UWB基站上放置两个相同且间隔d<λ/2的天线&#xff0c;UWB标签上的信号到达两个天线的相位差就在-180…

Docker Cgroups资源控制

Cgroup资源控制 Docker 通过 Cgroup 来控制容器使用的资源配额&#xff0c;包括 CPU、内存、磁盘三大方面&#xff0c; 基本覆盖了常见的资源配额和使用量控制。 Cgroup 是 ControlGroups 的缩写&#xff0c;是 Linux 内核提供的一种可以限制、记录、隔离进程组所使用的物理资源…

无为WiFi的一批服务器

我们在多个地区拥有高速服务器&#xff0c;保证网速给力&#xff0c;刷片无压力 嘿嘿 <?phpinclude("./includes/common.php"); $actisset($_GET[act])?daddslashes($_GET[act]):null; $urldaddslashes($_GET[url]); $authcodedaddslashes($_GET[authcode]);he…

多无人机编队集群飞行

matlab2016b可直接运行 多无人机集群编队飞行&#xff08;8架无人机&#xff09;资源-CSDN文库

逻辑回归评分卡

文章目录 一、基础知识点(1)逻辑回归表达式(2)sigmoid函数的导数损失函数(Cross-entropy, 交叉熵损失函数)交叉熵求导准确率计算评估指标 二、导入库和数据集导入库读取数据 三、分析与训练四、模型评价ROC曲线KS值再做特征筛选生成报告 五、行为评分卡模型表现总结 一、基础知…

manual control lost 飞机乱飞

Gazebo或jmavsim里仿真都这样&#xff0c;突然QGC会出现 manual control lost&#xff0c;然后飞机会乱飞 解决方案1&#xff1a; 把 NAV_RCL_ACT 设置为 Disable&#xff0c;相当于关闭遥控器丢失失效保护&#xff0c;默认是Return返航&#xff0c;所以会乱飞。 解决方案2&a…

实体机 安装 centos

实体机 安装 centos 制作U盘的时候&#xff0c;使用的ultraISO 同样方法一个u盘制作的有问题&#xff0c; 另外一个制作的没有问题。 可能和选择 usb-hdd 或者 usb-hdd 有关 https://mirrors.tuna.tsinghua.edu.cn/centos/7.9.2009/isos/x86_64/ 参考文档&#xff1a; http:…

《Python 自动化办公应用大全》书籍推荐(包邮送书五本)

前言 随着科技的快速发展和智能化办公的需求增加&#xff0c;Python自动化办公成为了一种趋势。Python作为一种高级编程语言&#xff0c;具有简单易学、功能强大和开放源代码等优势&#xff0c;可以帮助我们更高效地完成日常办公任务。 Python自动化办公还可以帮助我们实现更…

华为数通方向HCIP-DataCom H12-831题库(单选题:221-240)

第221题 以下哪些项能被正则表达式^30.成功匹配? A、200 100 300 B、100 200 300 C、300 200 100 D、300 100 200 答案:CD 解析: 30.其中的“点”表示的是任何的一个数字,表示的是as-path的开头;所以以300开头的都是满足题目需求的。 第222题 以下哪些项的Community属性能…

厌烦了iPhone默认的热点名称?如何更改iPhone上的热点名称

你对你默认的热点名称感到厌倦了吗&#xff1f;这篇文章是为你准备的。在这里&#xff0c;你可以了解如何轻松更改iPhone上的热点名称。 个人热点会将你的手机数据转换为Wi-Fi信号。手机上的个人热点使用户能够与其他用户共享其蜂窝数据连接。当你在WIFI网络之外时&#xff0c…

时序预测 | MATLAB实现ICEEMDAN-IMPA-GRU时间序列预测

时序预测 | MATLAB实现ICEEMDAN-IMPA-GRU时间序列预测 目录 时序预测 | MATLAB实现ICEEMDAN-IMPA-GRU时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 ICEEMDAN-IMPA-GRU功率/风速预测 基于改进的自适应经验模态分解改进海洋捕食者算法门控循环单元时间序列预…

通过IP地址管理提升企业网络安全防御

在今天的数字时代&#xff0c;企业面临着越来越多的网络安全威胁。这些威胁可能来自各种来源&#xff0c;包括恶意软件、网络攻击和数据泄露。为了提高网络安全防御&#xff0c;企业需要采取一系列措施&#xff0c;其中IP地址管理是一个重要的方面 1. IP地址的基础知识 首先&a…

常见弯道输送机有哪些

提到弯道输送机您可能首先想到的就是弯道滚筒线&#xff0c;其实除了滚筒线之外&#xff0c;也有一些其他线体可以做弯道&#xff0c;下面就为您总结了4种常见的弯道输送机。 1、弯道皮带线&#xff1a;即线体转弯处设计成皮带输送机&#xff0c;这种形式的转弯设计可以实现不同…

【ElasticSearch】基于 Java 客户端 RestClient 实现对 ElasticSearch 索引库、文档的增删改查操作,以及文档的批量导入

文章目录 前言一、对 Java RestClient 的认识1.1 什么是 RestClient1.2 RestClient 核心类&#xff1a;RestHighLevelClient 二、使用 Java RestClient 操作索引库2.1 根据数据库表编写创建 ES 索引的 DSL 语句2.2 初始化 Java RestClient2.2.1 在 Spring Boot 项目中引入 Rest…

ChatGPT多模态升级,支持图片和语音,体验如何?

一、前言 9 月 25 日&#xff0c;ChatGPT 多模态增加了新的语音功能和图像功能。这些功能提供了一种新的、更直观的界面&#xff0c;允许我们与 ChatGPT 进行语音对话或展示我们正在谈论的内容。 ChatGPT 现在可以看、听、和说话了&#xff0c;而不单单是一个文本驱动的工具了。…

Android攻城狮学鸿蒙 -- 点击事件

具体参考&#xff1a;华为官网学习地址 1、点击事件&#xff0c;界面跳转 对于一个按钮设置点击事件&#xff0c;跳转页面。但是onclick中&#xff0c;如果pages前边加上“/”&#xff0c;就没法跳转。但是开发工具加上“/”才会给出提示。不知道是不是开发工具的bug。&#…

Charles:移动端抓包 / windows客户端 iOS手机 / 手机访问PC本地项目做调试

一、背景描述 1.1、本文需求&#xff1a;移动端进行抓包调试 1.2、理解Charles可以做什么 Charles是一款跨平台的网络代理软件&#xff0c;可以用于捕获和分析网络流量&#xff0c;对HTTP、HTTPS、HTTP/2等协议进行调试和监控。使用Charles可以帮助开发人员进行Web开发、调试…

解决:使用WileyNJDv5_Template模板时,无法生成pdf文件。

目录 问题&#xff1a; 解决办法&#xff1a; 检查过程&#xff1a; WileyNJDv5-Template模板链接&#xff1a;New Journal Design LaTeX template (wiley.com) 问题&#xff1a; 使用wileyNJDv5_Template模板时候&#xff0c;无法生成pdf文件。无论是使用texlivetexmaker还…

设计模式_模板方法模式

模板方法模式 前言 行为型设计模式 关注对象和行为的分离。 关于父类与子类 调用时候 具体调用的哪一个&#xff1f; 普通方法调用编译时决定左边决定抽象/虚方法调用运行时决定右边决定 介绍 设计模式定义案例模板方法模式父类 定义了业务流程&#xff0c;其中一部分 延…