十五、异常(3)

本章概要

  • 捕获所有异常
    • 多重捕获
    • 栈轨迹
    • 重新抛出异常
    • 精准的重新抛出异常
    • 异常链

捕获所有异常

可以只写一个异常处理程序来捕获所有类型的异常。通过捕获异常类型的基类 Exception,就可以做到这一点(事实上还有其他的基类,但 Exception 是所有编程行为相关的基类):

catch(Exception e) {System.out.println("Caught an exception");
}

这将捕获所有异常,所以最好把它放在处理程序列表的末尾,以防它抢在其他处理程序之前先把异常捕获了。

因为 Exception 是与编程有关的所有异常类的基类,所以它不会含有太多具体的信息,不过可以调用它从其基类 Throwable 继承的方法:

String getMessage()
String getLocalizedMessage()

用来获取详细信息,或用本地语言表示的详细信息。

String toString()

返回对 Throwable 的简单描述,要是有详细信息的话,也会把它包含在内。

void printStackTrace()
void printStackTrace(PrintStream)
void printStackTrace(java.io.PrintWriter)

打印 Throwable 和 Throwable 的调用栈轨迹。调用栈显示了“把你带到异常抛出地点”的方法调用序列。其中第一个版本输出到标准错误,后两个版本允许选择要输出的流。

Throwable fillInStackTrace()

用于在 Throwable 对象的内部记录栈帧的当前状态。这在程序重新抛出错误或异常(很快就会讲到)时很有用。

此外,也可以使用 Throwable 从其基类 Object(也是所有类的基类)继承的方法。对于异常来说,getClass() 也许是个很好用的方法,它将返回一个表示此对象类型的对象。然后可以使用 getName() 方法查询这个 Class 对象包含包信息的名称,或者使用只产生类名称的 getSimpleName() 方法。

下面的例子演示了如何使用 Exception 类型的方法:

// exceptions/ExceptionMethods.java
// Demonstrating the Exception Methods
public class ExceptionMethods {public static void main(String[] args) {try {throw new Exception("My Exception");} catch (Exception e) {System.out.println("Caught Exception");System.out.println("getMessage():" + e.getMessage());System.out.println("getLocalizedMessage():" + e.getLocalizedMessage());System.out.println("toString():" + e);System.out.println("printStackTrace():");e.printStackTrace(System.out);}}
}

输出为:

在这里插入图片描述

可以发现每个方法都比前一个提供了更多的信息一一实际上它们每一个都是前一个的超集。

多重捕获

如果有一组具有相同基类的异常,你想使用同一方式进行捕获,那你直接 catch 它们的基类型。但是,如果这些异常没有共同的基类型,在 Java 7 之前,你必须为每一个类型编写一个 catch:

// exceptions/SameHandler.java
class EBase1 extends Exception {
}class Except1 extends EBase1 {
}class EBase2 extends Exception {
}class Except2 extends EBase2 {
}class EBase3 extends Exception {
}class Except3 extends EBase3 {
}class EBase4 extends Exception {
}class Except4 extends EBase4 {
}public class SameHandler {void x() throws Except1, Except2, Except3, Except4 {}void process() {}void f() {try {x();} catch (Except1 e) {process();} catch (Except2 e) {process();} catch (Except3 e) {process();} catch (Except4 e) {process();}}
}

通过 Java 7 的多重捕获机制,你可以使用“或”将不同类型的异常组合起来,只需要一行 catch 语句:

// exceptions/MultiCatch.java
public class MultiCatch {void x() throws Except1, Except2, Except3, Except4 {}void process() {}void f() {try {x();} catch (Except1 | Except2 | Except3 | Except4 e) {process();}}
}

或者以其他的组合方式:

// exceptions/MultiCatch2.java
public class MultiCatch2 {void x() throws Except1, Except2, Except3, Except4 {}void process1() {}void process2() {}void f() {try {x();} catch (Except1 | Except2 e) {process1();} catch (Except3 | Except4 e) {process2();}}
}

这对书写更整洁的代码很有帮助。

栈轨迹

printStackTrace() 方法所提供的信息可以通过 getStackTrace() 方法来直接访问,这个方法将返回一个由栈轨迹中的元素所构成的数组,其中每一个元素都表示栈中的一桢。元素 0 是栈顶元素,并且是调用序列中的最后一个方法调用(这个 Throwable 被创建和抛出之处)。数组中的最后一个元素和栈底是调用序列中的第一个方法调用。下面的程序是一个简单的演示示例:

// exceptions/WhoCalled.java
// Programmatic access to stack trace information
public class WhoCalled {static void f() {
// Generate an exception to fill in the stack tracetry {throw new Exception();} catch (Exception e) {for (StackTraceElement ste : e.getStackTrace()) {System.out.println(ste.getMethodName());}}}static void g() {f();}static void h() {g();}public static void main(String[] args) {f();System.out.println("*******");g();System.out.println("*******");h();}
}

输出为:

在这里插入图片描述

这里,我们只打印了方法名,但实际上还可以打印整个 StackTraceElement,它包含其他附加的信息。

重新抛出异常

有时希望把刚捕获的异常重新抛出,尤其是在使用 Exception 捕获所有异常的时候。既然已经得到了对当前异常对象的引用,可以直接把它重新抛出:

catch(Exception e) {System.out.println("An exception was thrown");throw e;
}

重抛异常会把异常抛给上一级环境中的异常处理程序,同一个 try 块的后续 catch 子句将被忽略。此外,异常对象的所有信息都得以保持,所以高一级环境中捕获此异常的处理程序可以从这个异常对象中得到所有信息。

如果只是把当前异常对象重新抛出,那么 printStackTrace() 方法显示的将是原来异常抛出点的调用栈信息,而并非重新抛出点的信息。要想更新这个信息,可以调用 fillInStackTrace() 方法,这将返回一个 Throwable 对象,它是通过把当前调用栈信息填入原来那个异常对象而建立的,就像这样:

// exceptions/Rethrowing.java
// Demonstrating fillInStackTrace()
public class Rethrowing {public static void f() throws Exception {System.out.println("originating the exception in f()");throw new Exception("thrown from f()");}public static void g() throws Exception {try {f();} catch (Exception e) {System.out.println("Inside g(), e.printStackTrace()");e.printStackTrace(System.out);throw e;}}public static void h() throws Exception {try {f();} catch (Exception e) {System.out.println("Inside h(), e.printStackTrace()");e.printStackTrace(System.out);throw (Exception) e.fillInStackTrace();}}public static void main(String[] args) {try {g();} catch (Exception e) {System.out.println("main: printStackTrace()");e.printStackTrace(System.out);}try {h();} catch (Exception e) {System.out.println("main: printStackTrace()");e.printStackTrace(System.out);}}
}

输出为:

在这里插入图片描述

调用 fillInStackTrace() 的那一行就成了异常的新发生地了。

有可能在捕获异常之后抛出另一种异常。这么做的话,得到的效果类似于使用 fillInStackTrace(),有关原来异常发生点的信息会丢失,剩下的是与新的抛出点有关的信息:

// exceptions/RethrowNew.java
// Rethrow a different object from the one you caught
class OneException extends Exception {OneException(String s) {super(s);}
}class TwoException extends Exception {TwoException(String s) {super(s);}
}public class RethrowNew {public static void f() throws OneException {System.out.println("originating the exception in f()");throw new OneException("thrown from f()");}public static void main(String[] args) {try {try {f();} catch (OneException e) {System.out.println("Caught in inner try, e.printStackTrace()");e.printStackTrace(System.out);throw new TwoException("from inner try");}} catch (TwoException e) {System.out.println("Caught in outer try, e.printStackTrace()");e.printStackTrace(System.out);}}
}

输出为:

在这里插入图片描述

最后那个异常仅知道自己来自 main(),而对 f() 一无所知。

永远不必为清理前一个异常对象而担心,或者说为异常对象的清理而担心。它们都是用 new 在堆上创建的对象,所以垃圾回收器会自动把它们清理掉。

精准的重新抛出异常

在 Java 7 之前,如果捕捉到一个异常,重新抛出的异常类型只能与原异常完全相同。这导致代码不精确,Java 7修复了这个问题。所以在 Java 7 之前,这无法编译:

class BaseException extends Exception {
}class DerivedException extends BaseException {
}public class PreciseRethrow {void catcher() throws DerivedException {try {throw new DerivedException();} catch (BaseException e) {throw e;}}
}

因为 catch 捕获了一个 BaseException,编译器强迫你声明 catcher() 抛出 BaseException,即使它实际上抛出了更具体的 DerivedException。从 Java 7 开始,这段代码就可以编译,这是一个很小但很有用的修复。

异常链

常常会想要在捕获一个异常后抛出另一个异常,并且希望把原始异常的信息保存下来,这被称为异常链。在 JDK1.4 以前,程序员必须自己编写代码来保存原始异常的信息。现在所有 Throwable 的子类在构造器中都可以接受一个 cause(因由)对象作为参数。这个 cause 就用来表示原始异常,这样通过把原始异常传递给新的异常,使得即使在当前位置创建并抛出了新的异常,也能通过这个异常链追踪到异常最初发生的位置。

有趣的是,在 Throwable 的子类中,只有三种基本的异常类提供了带 cause 参数的构造器。它们是 Error(用于 Java 虚拟机报告系统错误)、Exception 以及 RuntimeException。如果要把其他类型的异常链接起来,应该使用 initCause() 方法而不是构造器。

下面的例子能让你在运行时动态地向 DynamicFields 对象添加字段:

// exceptions/DynamicFields.java
// A Class that dynamically adds fields to itself to
// demonstrate exception chaining
class DynamicFieldsException extends Exception {
}public class DynamicFields {private Object[][] fields;public DynamicFields(int initialSize) {fields = new Object[initialSize][2];for (int i = 0; i < initialSize; i++) {fields[i] = new Object[]{null, null};}}@Overridepublic String toString() {StringBuilder result = new StringBuilder();for (Object[] obj : fields) {result.append(obj[0]);result.append(": ");result.append(obj[1]);result.append("\n");}return result.toString();}private int hasField(String id) {for (int i = 0; i < fields.length; i++) {if (id.equals(fields[i][0])) {return i;}}return -1;}private int getFieldNumber(String id)throws NoSuchFieldException {int fieldNum = hasField(id);if (fieldNum == -1) {throw new NoSuchFieldException();}return fieldNum;}private int makeField(String id) {for (int i = 0; i < fields.length; i++) {if (fields[i][0] == null) {fields[i][0] = id;return i;}}// No empty fields. Add one:Object[][] tmp = new Object[fields.length + 1][2];for (int i = 0; i < fields.length; i++) {tmp[i] = fields[i];}for (int i = fields.length; i < tmp.length; i++) {tmp[i] = new Object[]{null, null};}fields = tmp;// Recursive call with expanded fields:return makeField(id);}public ObjectgetField(String id) throws NoSuchFieldException {return fields[getFieldNumber(id)][1];}public Object setField(String id, Object value)throws DynamicFieldsException {if (value == null) {// Most exceptions don't have a "cause"// constructor. In these cases you must use// initCause(), available in all// Throwable subclasses.DynamicFieldsException dfe = new DynamicFieldsException();dfe.initCause(new NullPointerException());throw dfe;}int fieldNumber = hasField(id);if (fieldNumber == -1) {fieldNumber = makeField(id);}Object result = null;try {result = getField(id); // Get old value} catch (NoSuchFieldException e) {// Use constructor that takes "cause":throw new RuntimeException(e);}fields[fieldNumber][1] = value;return result;}public static void main(String[] args) {DynamicFields df = new DynamicFields(3);System.out.println(df);try {df.setField("d", "A value for d");df.setField("number", 47);df.setField("number2", 48);System.out.println(df);df.setField("d", "A new value for d");df.setField("number3", 11);System.out.println("df: " + df);System.out.println("df.getField(\"d\") : "+ df.getField("d"));Object field =df.setField("d", null); // Exception} catch (NoSuchFieldException |DynamicFieldsException e) {e.printStackTrace(System.out);}}
}

输出为:

在这里插入图片描述

每个 DynamicFields 对象都含有一个数组,其元素是“成对的对象”。第一个对象表示字段标识符(一个字符串),第二个表示字段值,值的类型可以是除基本类型外的任意类型。当创建对象的时候,要合理估计一下需要多少字段。当调用 setField() 方法的时候,它将试图通过标识修改已有字段值,否则就建一个新的字段,并把值放入。如果空间不够了,将建立一个更长的数组,并把原来数组的元素复制进去。如果你试图为字段设置一个空值,将抛出一个 DynamicFieldsException 异常,它是通过使用 initCause() 方法把 NullPointerException 对象插入而建立的。

至于返回值,setField() 将用 getField() 方法把此位置的旧值取出,这个操作可能会抛出 NoSuchFieldException 异常。如果客户端程序员调用了 getField() 方法,那么他就有责任处理这个可能抛出的 NoSuchFieldException 异常,但如果异常是从 setField() 方法里抛出的,这种情况将被视为编程错误,所以就使用接受 cause 参数的构造器把 NoSuchFieldException 异常转换为 RuntimeException 异常。

你会注意到,toString() 方法使用了一个 StringBuilder 来创建其结果。在 字符串 这章中你将会了解到更多的关于 StringBuilder 的知识,但是只要你编写设计循环的 toString() 方法,通常都会想使用它,就像本例一样。

main() 方法中的 catch 子句看起来不同 - 它使用相同的子句处理两种不同类型的异常,这两种不同的异常通过“或(|)”符号结合起来。 Java 7 的这项功能有助于减少代码重复,并使你更容易指定要捕获的确切类型,而不是简单地捕获一个基类型。你可以通过这种方式组合多种异常类型。

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

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

相关文章

鸿鹄工程项目管理系统 Spring Cloud+Spring Boot+Mybatis+Vue+ElementUI+前后端分离构建工程项目管理系统

项目背景 一、随着公司的快速发展&#xff0c;企业人员和经营规模不断壮大。为了提高工程管理效率、减轻劳动强度、提高信息处理速度和准确性&#xff0c;公司对内部工程管理的提升提出了更高的要求。 二、企业通过数字化转型&#xff0c;不仅有利于优化业务流程、提升经营管理…

HTML——列表,表格,表单内容的讲解

文章目录 一、列表1.1无序&#xff08;unorder&#xff09;列表1.2 有序&#xff08;order&#xff09;列表1.3 定义列表 二、表格**2.1 基本的表格标签2.2 演示 三、表单3.1 form元素3.2 input元素3.2.1 单选按钮 3.3 selcet元素 基础部分点击&#xff1a; web基础 一、列表 …

【JavaEE】CAS(Compare And Swap)操作

文章目录 什么是 CASCAS 的应用如何使用 CAS 操作实现自旋锁CAS 的 ABA 问题CAS 相关面试题 什么是 CAS CAS&#xff08;Compare and Swap&#xff09;是一种原子操作&#xff0c;用于在无锁情况下保证数据一致性的问题。它包含三个操作数——内存位置、预期原值及更新值。在执…

轻量自高斯注意力(LSGA)机制

light&#xff08;轻量&#xff09;Self-Gaussian-Attention vision transformer&#xff08;高斯自注意力视觉transformer&#xff09; for hyperspectral image classification&#xff08;高光谱图像分类&#xff09; 论文&#xff1a;Light Self-Gaussian-Attention Vision…

完整指南:如何使用 Node.js 复制文件

文件拷贝指的是将一个文件的数据复制到另一个文件中&#xff0c;使目标文件与源文件内容一致。Node.js 提供了文件系统模块 fs&#xff0c;通过该模块可以访问文件系统&#xff0c;实现文件操作&#xff0c;包括拷贝文件。 Node.js 中文件拷贝方法 在 Node.js 中&#xff0c;有…

基于微信小程序的宠物寄养平台小程序设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言系统主要功能&#xff1a;具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计…

预编译(1)

目录 预定义符号&#xff1a; 使用&#xff1a; 结果&#xff1a; 预编译前后对比&#xff1a; #define定义常量&#xff1a; 基本语法&#xff1a; 举例1&#xff1a; 结果&#xff1a; 预编译前后对比&#xff1a; 举例2&#xff1a; 预编译前后对比&#xff1a; 注…

ELK介绍

一、前言 前面的章节我们介绍通过ES Client将数据同步到ElasticSearch中&#xff0c;但是像日志这种数据没有必要自己写代码同步到ES那样会折腾死&#xff0c;直接采用ELK方案就好&#xff0c;ELK是Elasticsearch、Logstash、Kibana三款开源软件的缩写&#xff0c;ELK主要用于…

P2PNet-Soy原理梳理

前文总结了P2PNet源码以及P2PNet-Soy源码实现方法&#xff0c;相关链接如下&#xff1a; 人群计数P2PNet论文&#xff1a;[2107.12746] Rethinking Counting and Localization in Crowds:A Purely Point-Based Framework (arxiv.org) p2p人群计数源码&#xff1a;GitHub - Te…

云服务器租用价格表概览_阿里云腾讯云华为云

云服务器租用价格多少钱一年&#xff1f;阿腾云分享阿里云、腾讯云和华为云的云服务器租用价格表&#xff1a;阿里云2核2G服务器108元一年起、腾讯云2核2G3M带宽轻量服务器95元一年、华为云2核2G3M云耀L实例89元一年起&#xff0c;阿腾云分享更多关于云服务器租用价格明细&…

Kubernetes基础(五)-Service

1 引言 Service 主要用于提供网络服务&#xff0c;通过Servicel的定义&#xff0c;能够 为客户端应用提供稳定的访问地址&#xff08;域名或IP地址&#xff09;和负载均衡功能&#xff0c;以及屏蔽后端Endpoint的变化&#xff0c;是Kubernetes实现微服务的核心资源。 本文详细…

博弈论中静态博弈经典场景案例

博弈论中静态博弈经典场景案例 1、齐威王田忌赛马 田忌赛马是中国家喻户晓的故事&#xff0c;故事讲述的是齐国大将田忌的谋士孙膑如何运用计谋帮助田忌在与齐威王赛马时以弱胜强的故事&#xff0c;这个故事其实本质也是一个博弈的过程。     齐威王要和田忌赛马&#xff…

二叉树MFC实现

设有一颗二叉树如下&#xff1b; 这似乎是一颗经常用作示例的二叉树&#xff1b; 对树进行遍历的结果是&#xff0c; 先序为&#xff1a;3、2、2、3、8、6、5、4&#xff0c; 中序为&#xff1a;2、2、3、3、4、5、6、8&#xff0c; 后序为2、3、2、4、5、6、8、3&#xff1b…

MySQL学习笔记25

逻辑备份 物理备份 在线热备&#xff1a; 真实案例&#xff1a; 数据库架构是一主两从&#xff0c;但是两台从数据库和主数据不同步。但是每天会全库备份主服务器上的数据到从服务器上。需要解决主从不同步的问题。 案例背后的核心技术&#xff1a; 1、熟悉MySQL数据库常见…

【计算机视觉|人脸建模】PanoHead:360度几何感知的3D全头合成

本系列博文为深度学习/计算机视觉论文笔记&#xff0c;转载请注明出处 标题&#xff1a;PanoHead: Geometry-Aware 3D Full-Head Synthesis in 360 ∘ ^{\circ} ∘ 链接&#xff1a;[2303.13071] PanoHead: Geometry-Aware 3D Full-Head Synthesis in 360 ∘ ^{\circ} ∘ (arx…

大数据Doris(三):Doris编译部署篇

文章目录 Doris编译部署篇 一、Doris编译

学信息系统项目管理师第4版系列13_立项管理

1. 项目立项管理包括 1.1. 项目建议与立项申请 1.2. 项目可行性研究 1.2.1. 初步可行性研究 1.2.2. 详细可行性研究 1.2.2.1. 不可缺少 1.2.2.1.1. 【高21上选21】 1.2.3. 可以依据项目的规模和繁简程度合二为一 1.3. 项目评估与决策 2. 立项申请 2.1. 项目建议书 2…

Lua语法之简单变量

--nil有点类似空null a nil print(a) --type函数得到类型 返回值是string print(type(a)) print("*****")--number是数值 int float这些 --lua的变量可以随便赋值 自动识别类型 a 1 print(a) print(type(a)) print("*****")--siting可以用单引号双引号 a…

华为云HECS云服务器docker环境下安装nginx

前提&#xff1a;有一台华为云服务器。 华为云HECS云服务器&#xff0c;安装docker环境&#xff0c;查看如下文章。 华为云HECS安装docker-CSDN博客 一、拉取镜像 下载最新版Nginx镜像 (其实此命令就等同于 : docker pull nginx:latest ) docker pull nginx查看镜像 dock…

实体行业数字化转型怎么做?线上线下相结合的新零售体系怎么做?

如今&#xff0c;实体行业想要取得收入增长&#xff0c;只做线下业务或者只做线上业务&#xff0c;在当前的市场环境中是难以长久生存的&#xff0c;因此一定要线上线下相结合&#xff0c;将流量运作与线下转化进行充分结合&#xff0c;才能更好地发挥实体优势&#xff0c;带来…