【注】本文译自:
Java Exception
Java Exception 是为处理异常应用程序行为而创建的类。在本文中,我将解释如何使用 Java Exception 类以及如何在考虑现有 Java Exceptions 设计的情况下创建异常结构。Java 异常概念是 Java 中的重要里程碑之一,每个开发人员都 必须 了解它。
Java 异常结构比你想象的要有用
Java 异常的结构非常有用,可以告诉开发人员一组重要的事情(如果开发人员正确使用此结构)。所以,在这里,您可以看到基本结构:
可以捕获所有可能情况的主要父类是 Throwable,它有 2 个子类:Error 和 Exception。
Java Error
Java Error 代表异常情况。一旦出现错误,应用程序可能会关闭。
Java Exception
与错误不同,Java 异常有机会从问题中恢复应用程序,并尝试保持应用程序运行。异常也分为两类:
异常由运行时和非运行时异常表示,也称为已检异常。此分类与错误异常非常相似,但在该分类中,已检异常在恢复方面更为乐观。
已检和未检异常
在 Java 中,有两种类型的异常。 已检 异常迫使开发人员创建处理程序异常或重新抛出它们。如果重新抛出已检查的异常,则 java 函数必须在其签名中声明它。 未检 异常 unline checked 不需要任何处理。 这样的设计意味着无法处理未经检查的异常,并且注定会被抛出到顶级父类。
Error 异常调查
有两种方法可以处理抛出的异常:在当前方法中处理它或者只是重新抛出它。没有比这更好的方法了:您可能有一个父处理程序或以某种方式处理它,例如创建重试逻辑。
好的、坏的、丑的
介绍完之后,我们可以将所有异常分为 3 组:Checked、Runtime 和 Error。主要思想是,他们每个人都会陷入不同的情况。最乐观的是 Checked 异常。运行时将属于恢复机会很小的情况。而且,最悲观的是Error。
Checked, Runtime, Error … 然后呢?
了解异常类的类型后,我们 可能会 回答下一个问题:
- 情况有多糟糕,问题的原因是什么。
- 如何解决这个问题。
- 我们需要重启 JVM 吗?
- 我们需要重写代码吗?
知道异常类,我们可以预测可能出错的地方。考虑潜在的原因,我们可以假设问题的原因是什么以及如何解决它。让我们回顾一下最流行的场景,看看这些异常可以告诉我们什么。在接下来的段落中,我们将回顾著名的异常并调查潜在的代码是什么。在我们的调查中,我们假设应用程序足够稳定并且开发阶段已经完成和测试。
Error 异常调查
我们从最悲观的案例或我们的丑男开始。 Error 真的有那么丑吗? 让我们来看看最常见的 Java 错误:
| 潜在原因 | 发生机率 | 如何修复 | 是否需要重写代码? | 是否需要重启 JVM? |
OutOfMemory | 应用程序吃掉了所有内存 | 高 | 增加堆内存大小 | 否 | 是 |
内存泄漏 | 低 | 查找内存泄漏并修复 | 是 | 是 | |
StackOverFlow | 堆栈内存不足 | 高 | 增加堆栈内存大小 | 否 | 是 |
无限递归 | 低 | 设置递归调用的限制 | 是 | 是 | |
NoClassDefFoundError | 缺少依赖 | 高 | 添加依赖或修复依赖配置 | 否 | 是 |
初始化期间加载类失败 | 低 | 更改初始化过程 | 是 | 是 |
因此,在大多数情况下,您需要做的就是更改 JVM 配置或添加缺少的依赖项。仍然存在需要更改代码的情况,但它们不太可能在每种情况下应用更改。
对于受检异常,我们期望有机会恢复问题;例如,再试一次。在这一部分,我们回顾最著名的已检异常。提供的例外可能是彼此的父类,但是,在这里,我只列出最常见的案例,而不关心它们的关系:
| 潜在原因 | 发生机率 | 如何修复 | 是否需要重写代码? | 是否需要重启 JVM? |
FileNotFoundException | 文件不存在 | 高 | 创建文件 | 否 | 否 |
应用程序调用错误的路径 | 低 | 修复错误的路径生成 | 是 | 是 | |
IOException | 访问资源无效 | 高 | 让资源再次可用 | 否 | 否 |
ClassNotFoundException | 该类未添加依赖项 | 高 | 添加缺少的依赖项 | 否 | 是 |
实现调用了错误的类 | 中 | 更改类调用 | 是 | 是 | |
SqlException | 模式与查询不匹配 | 高 | 将缺失的脚本应用到数据库 | 否 | 否 |
查询错误 | 低 | 更改查询 | 是 | 是 | |
拒绝连接 | 高 | 打开数据库,更改端口 | 否 | 否 | |
Interrupted Exception | 依赖线程通知中断(锁释放,另一个线程完成操作) | 高 | 没有必要修复它; 这是一种通知相关线程中事件的方法 | 否 | 否 |
另一个线程中断并使用中断通知相关 | 中 | 修复另一个线程中出现的问题(可以是任何东西) | 是 | 是 | |
Socket Exception | 端口被占用 | 高 | 打开/释放端口 | 否 | 否 |
服务器断开连接 | 高 | 检查网络连接 | 否 | 否 |
好吧,有很多异常,但是,正如我所承诺的,我把最常见的异常放在这里。那么,这张表说明了什么?如果我们查看最可能的原因,我们会发现其中的 大多数 不仅不需要任何代码更改,甚至不需要重新启动应用程序。所以,显然,已检异常应该是好人。
Runtime 异常调查
最常见也是个人最悲观的例外:运行时。Checked 和 Error 异常错误不会导致任何代码更改。但是,在大多数情况下,运行时异常突出了代码中的真正问题,如果不重写代码就无法修复这些问题。让我们通过查看最流行的运行时异常来找出原因:
| 潜在原因 | 发生机率 | 如何修复 | 是否需要重写代码? | 是否需要重启 JVM? |
NullPointerException | 预期的不可为空的对象为空 | 高 | 调用前添加验证层 | 是 | 是 |
某些资源不可用并返回空数据 | 中 | 调用前添加验证层 | 是 | 是 | |
ConcurrentModificationException | 迭代期间集合已更改 | 高 | 分别进行集合迭代和修改 | 是 | 是 |
集合在迭代期间已从另一个线程更改 | 高 | 为集合添加同步 | 是 | 是 | |
IlliegalArgumentException | 传递的参数无效 | 高 | 在传递参数之前添加验证 | 是 | 是 |
NumberFormatException | 传递的参数格式错误或符号错误 | 高 | 在传递数据之前添加格式或删除不可见符号 | 是 | 是 |
ArrayIndexOutOfBoundsException | 指令试图通过不存在的索引访问单元格 | 高 | 将访问逻辑更改为正确的逻辑 | 是 | 是 |
NoSuchElementException | 当指针已经改变位置时访问元素 | 高 | 将访问逻辑更改为正确的逻辑 | 是 | 是 |
集合在迭代过程中被修改 | 高 | 为集合添加同步 | 是 | 是 |
一个例子可能给人的印象是任何运行时异常都会导致应用程序失败。在大多数情况下,这是正确的,因为不更改代码就无法恢复应用程序。最终,运行时异常是我们的坏人,它会导致新的代码更改、开发人员的压力和业务损失。
些许批评
在这次审查期间,我们做出了一个重大假设:代码已准备好投入生产并经过充分测试。但是,在实践中,这很难实现。所以,我们所做的结论并不是100% 可靠,但是代码越稳定,结果就越真实。
已检异常和代码污染
根据已检查异常,设计开发人员必须使所有可恢复的异常都是可检查的。因此,每次调用带有已检查异常签名的方法都会为 Try Catch 结构添加 3-4 行。这种方法使代码变得丑陋且可读性较差。就个人而言,我更喜欢使用运行时异常。即使在设计库的情况下,您仍然可以在方法签名中保留运行时异常,并在 API 中添加一些注释。在这种情况下,您的 API 用户将能够决定如何处理它。