深入剖析 Java 新特性
范学雷
前 Oracle 首席软件工程师,Java SE 安全组成员,OpenJDK 评审成员
16539 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 23 讲
深入剖析 Java 新特性
15
15
1.0x
00:00/00:00
登录|注册

09 | 异常恢复,付出的代价能不能少一点?

你好,我是范学雷。今天,我们接着讨论 Java 的错误处理。这一讲,是上一次我们讨论的关于错误处理问题的继续和升级。
就像我们上一次讨论到的,Java 的异常处理是一个对代码性能有着重要影响的因素。所以说,Java 错误处理的缺陷和滥用也成为了一个热度始终不减的老话题。但是,Java 的异常处理,有着天生的优势,特别是它在错误排查方面的作用,我们很难找到合适的替代方案。
那有没有可能改进 Java 的异常处理,保持它在错误排查方面的优势的同时,提高它的性能呢?这是一个又让马儿跑,又让马儿不吃草的问题。不过,这并不妨碍我们顺着这个思路,找一找其中的可能性。
我们还是先从阅读案例开始,来试着找一找其中的蛛丝马迹吧。

阅读案例

要尝试解决一个问题,我们首先要做的,就是把问题梳理清楚,定义好。我们先来看看 Java 异常处理的三个典型使用场景。
下面的这段代码里,有三个不同的异常使用方法。在分别解析的过程中,你可能会遇到几个疑问,不过别急,带着这几个问题,我们最后来一一解读。
package co.ivi.jus.stack.former;
import java.security.NoSuchAlgorithmException;
public class UseCase {
public static void main(String[] args) {
String[] algorithms = {"SHA-128", "SHA-192"};
String availableAlgorithm = null;
for (String algorithm : algorithms) {
Digest md;
try {
md = Digest.of(algorithm);
} catch (NoSuchAlgorithmException ex) {
// ignore, continue to use the next algorithm.
continue;
}
try {
md.digest("Hello, world!".getBytes());
} catch (Exception ex) {
System.getLogger("co.ivi.jus.stack.former")
.log(System.Logger.Level.WARNING,
algorithm + " does not work",
ex);
continue;
}
availableAlgorithm = algorithm;
}
if (availableAlgorithm != null) {
System.out.println(availableAlgorithm + " is available");
} else {
throw new RuntimeException("No available hash algorithm");
}
}
}
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文讨论了Java异常处理的优势和缺陷,以及如何改进Java的异常处理以提高性能。通过阅读案例分析了可恢复异常和不可恢复异常的处理方式,以及记录调试信息的重要性。文章提出了三个问题:可恢复异常是否需要生成调用堆栈、不可恢复异常的存在必要性以及放弃Java异常处理机制后是否能获得足够的调试信息。作者通过案例分析和问题提出,引发了对Java异常处理的深入思考,为读者提供了对Java异常处理的全面了解。文章还掏出了几个技术要点,可能在读者的面试中出现。文章最后提出了一个思考题,让读者思考如何通过改进Java的异常处理来获取性能的提升。文章内容涉及到了Java异常处理的两个概念:可恢复异常和不可恢复异常,以及在使用错误码的方案里,添加快速定位出问题的调试信息。同时,还探讨了如何处理多个错误码的问题,以提高代码的可维护性和健壮性。整体而言,本文对Java异常处理机制的现状和改进方向进行了深入的探讨,为读者提供了全面的思考和学习材料。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《深入剖析 Java 新特性》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(11)

  • 最新
  • 精选
  • Jxin
    调试信息能不能按需开启、关闭呢? 可以,成本比较大。举个例子。 1.实现生产环境一键镜像,难点是要包含生产所有数据(知识级与操作级都要) 2.基于OpenTelemetry把日志打印齐全 3.基于观测数据做链路可视化界面 4.链路可视化界面支持请求向不同环境重放(入参已有,不过是调指定环境的接口) 如此一来测试环境就拥有和生产环境一模一样的,你不仅可以在测试环境打印debug日志,还可以直接debug。

    作者回复: 是一个好思路,学习了。

    2021-12-03
    7
  • aoe
    这么巧!昨天刚看到 Go 语言处理异常的四种方式,有一种可以参考《Tony Bai · Go 语言第一课》| 22|函数:怎么结合多返回值进行错误处理?原文链接:https://time.geekbang.org/column/article/461821 策略四:错误行为特征检视策略 在 Go 标准库中,我们发现了这样一种错误处理方式:将某个包中的错误类型归类,统一提取出一些公共的错误行为特征,并将这些错误行为特征放入一个公开的接口类型中。这种方式也被叫做错误行为特征检视策略。 以标准库中的net包为例,它将包内的所有错误类型的公共行为特征抽象并放入net.Error这个接口中,如下面代码: ```go // $GOROOT/src/net/net.go type Error interface { error Timeout() bool // 断网、断电、网络拥堵、丢包等都可以造成超时 Temporary() bool } ``` 计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决。策略四将稀奇古怪的错误信息进行了抽象分类,将无限种可能转换成了有限种可能(将不可控转换成可控),遵循了开闭原则。 再回顾本专栏《深入剖析 Java 新特性》04 | 封闭类:怎么刹住失控的扩展性?中提到的怎么判断一个图形是正方形的例子可以帮助理解:特殊的长方形、菱形、梯形、多边形等都是正方形

    作者回复: 哈哈,编程语言本质上都是处理类似的问题,只是设计者的偏好可能不同。

    2021-12-03
    1
  • 鹤涵
    如何平衡手动打印异常logger的开发成本和异常性能开销呢?

    作者回复: 首先把异常用对,然后看系统需要。 很难有通用的法则。

    2022-11-27归属地:美国
  • james
    老师,方法实现和方法使用为啥输出两遍日志呢,方法实现里面输出一次不就行了

    作者回复: 一般情况下一次就行。不过有时候,方法的使用需要更好地控制日志信息,也可能会多次输出。

    2022-10-09归属地:美国
  • fatme
    老师,对于使用异常的情况。造成其性能下降的原因,是否应该包括两部分活动:一是生成调用栈的调试信息;另一个是把异常沿调用栈向上抛出,直到被捕捉或终止程序?如果是的话,其中哪一个部分造成的性能损耗更厉害呢?

    作者回复: 主要是生成堆栈信息的开销,向上抛的开销要小很多,除非中间有截获代码。

    2021-12-12
  • 零__轨迹
    请问老师,参考函数式中的Try来封装有异常的执行结果对官方来说是否可行呢?

    作者回复: 没有明白这个问题是什么意思? 能描述的详细一些吗,或者举个例子?

    2021-12-06
    3
  • bigben
    1、System.getLogger("co.ivi.jus.stack.union") .log(System.Logger.Level.INFO, "Unknown algorithm is specified " + algorithm, new Throwable("the calling stack")); 这个把日志关掉也不能提高多少性能,new Throwable("the calling stack")总是要收集堆栈信息的; 2、如果继续沿用 Java 的异常处理机制,调试信息可以按需开启、关闭吧,Throwable(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace)

    作者回复: 1、是的,我在代码里忽视了这个问题。“new Throwable”应该在日志启用的情况下调用,而不是总是被调用。 2、是的,这里的调试信息关闭,需要特殊的代码处理,而且性能的影响依然很大(我忘记是数十倍还是数百倍了,不过不再是几千倍的水平了)。我们期望的,是不需要更改代码的,几乎没有性能影响的改进。

    2021-12-03
    4
  • Calvin
    思考题我把 ErrorCode 许可类改成了枚举,至于编译器检测使用匹配(遗漏或多余),则是使用 switch 表达式,PR 在:https://github.com/XueleiFan/java-up/pull/16

    作者回复: 嗯,这是一个很好的思路。要是能解决更大范围内的适用性,就更好了。

    2021-12-03
  • 小飞同学
    感觉写的比较冗杂,希望各路大神指点下 Result rt = Digest.of("SHA-256"); switch (rt) { case Result.ErrorCode e -> { switch (e.errorCode){ case -1-> System.out.println("algorithm is null"); case -2-> System.out.println("algorithm is not support"); default -> { // other errorCode } } } case Result.ReturnValue rv -> { switch (rv.returnValue){ case Digest d -> d.digest("Hello, world!".getBytes()); default -> { // other type process } } } default -> { // other permit class } };

    作者回复: 想提交一个GitHub的PR吗?

    2021-12-03
  • kimoti
    感觉应该用switch表达式来穷举error code

    作者回复: 是一个思路。

    2021-12-03
收起评论
显示
设置
留言
11
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部