类似
int i = Integer.parseInt("e");
会抛出
java.lang.NumberFormatException
NumberFormatException 的隶属关系
Object
-Throwable
-Exception
-RuntimeException
-IllegalArgumentExcetipn
-NumberFormatException
挺正常,捕获 Exception 就能 catch 住他
然而如果这么写
static int i = Integer.parseInt("e");
会抛出
java.lang.ExceptionInitializerError ..
Caused by: java.lang.NumberFormatException ...
而 ExceptionInitializerError 的的隶属关系
Object
-Throwable
-Error
-LinkageError
-ExceptionInitializerError
这时,捕获 Exception 已经不能 catch 住这个异常了。需要捕获 Throwable 或者 Exception | Error 了。
然而同样都是做了一个Integer.parseInt("e")
,这时升格成为 static,就造成了异常类的变化。导致捕获方式的变化。这是不是很奇怪?
当然这里的Integer.parseInt("e")
,只是一个例子
如果你有你可爱的同学,在 static 块 /变量里面写了一大段复杂逻辑,搞出各种各样的 Exception,都会被升格成为ExceptionInitializerError
。从而不能被 catch Exception 所 catch
从类的初始化过程、从编译的角度貌似能够理解这个 LinkageError。但从具体逻辑的角度,这为啥呢?
其实这个还有一个问题就是有人说到的所谓的异常处理"军规",Never catch Throwable class。这点在这个情况下又如何理解呢?
1
darrenfang 2020-01-17 09:45:01 +08:00
这样写并不会有问题
```java static int i; static { try { i = Integer.parseInt("e"); } catch (Exception e) { } } ``` |
2
SpencerCJH 2020-01-17 09:48:18 +08:00
学到了
|
3
watzds 2020-01-17 09:52:09 +08:00 via Android
非常正常好吧,解析失败,i 没有值,初始化 i 失败,是两种错误
|
4
passerbytiny 2020-01-17 09:54:17 +08:00
你可以
`try{ ` int i = Integer.parseInt("e"); `}catch ... 但不可以 `try{ ` static int i = Integer.parseInt("e"); `}catch ... 所以前者抛出异常,后者抛出错误。Exception、Error 虽然都继承自 Throwable,但原则上前者捕获后允许处理使其消失,后者捕获后只能做日志、告警而不能处理——必须中断程序或者 5**返回。 |
5
palmers 2020-01-17 09:56:13 +08:00
我同意 3#的说法 两种异常一种是解析失败异常一种是初始化失败异常, 只不过后面又给包装了一下 就像我们常常将 api 的异常包装为自己的业务异常一样
|
6
lff0305 2020-01-17 10:01:06 +08:00
ExceptionInitializerError 初始化异常, 表示初始化这个类的时候出错了,至于为什么出错,具体原因是 NumberFormatException, 在 getCause() 里面
|
7
matepi OP @darrenfang @passerbytiny 有的时候,我们是面向别人的代码编程……你引入了一个包,甚至都没有源码的情况下。并不知道可爱的前任给你留了个这样的坑。
说真的,类似这种初始过程中异常性没完全包好的事情,其实是很多的,即便质量比较好的框架有时候也会和一些环境配置上搞出这样的事情。最终还会出一些类似类明明在,然后 NoClassDefFound 的异常。又类似 headless 那种环境性配置。 @palmers 如果有设计的包装这个是可以理解的。但问题就在于这个包装并不是你设计的。也不是前人设计的。是某种异常环境配置下才会出现的尚未设计的事情。 |
8
iFlicker 2020-01-17 10:34:25 +08:00
我困惑的是为什么在用 static 修饰 变量 的时候会捕获 ExceptionInitializerError 呢?
具体是在编译过程的词法分析吗? 有没有大佬解答一下~ |
9
chendy 2020-01-17 10:35:10 +08:00 2
静态变量要初始化,静态变量初始化不了类加载会有问题,类加载有问题类就用不了,类都用不了代码就跑不起来也就不用考虑去 catch 了,catch 住了也没用
|
10
momocraft 2020-01-17 10:38:51 +08:00
static 变量的求值发生在 class load 时?
|
12
szq8014 2020-01-17 11:04:12 +08:00
是同一个异常,都是 NumberFormatException ,只不过后面那个又给包装了一层,直接换成 error 扔出来了,毕竟是 static 的发生在类加载期,初始化失败当然不能继续运行了,抛个 Error 是可期的
|
13
shily 2020-01-17 11:18:16 +08:00
@iFlicker
@lff0305 # 6 的才是正确答案。 『都会被升格成为 ExceptionInitializerError。从而不能被 catch Exception 所 catch 』这个说法本身是错误的。 呃,怎么说呢,它并不是被升格,而是因为你不处理,导致 JVM 无法进行下去,JVM 崩溃了,为了进一步表示 JVM 为什么崩溃而包装了一下。 ExceptionInitializerError 表示类初始化的时候发生了异常,可以通过 getCause() 获知错误的具体原因。 呐如果我想自己处理 NumberFormatException 改怎么写呢? static int i = Integer.parseInt("e"); // 无法直接使用 try-catch 但是它等价于 static int i; static { i = Integer.parseInt("e"); } 这样你就可以自行处理其中抛出的异常了。 static int i; static { try { i = Integer.parseInt("e"); } catch (NumberFormatException nfe) { // handle exception here } } |
14
passerbytiny 2020-01-17 11:32:27 +08:00
@matepi #7 先讲技术,再找理由。
int i = Integer.parseInt(some 变量)才有可能出现异常,而在非演示目的的情况下写出来 int i = Integer.parseInt("e")这样的代码就是沙雕,所以此时抛出的是运行时异常——你无法避免它但是可以解决它。 static int i = Integer.parseInt("e")这明显是沙雕代码; static String someIntStr = "e"; static int i = Integer.parseInt( someIntStr )仍然是沙雕代码; static int i; void initOrUpdate(String aIntStrForInit){ if(Class.i == -1){ Class.i = Integer.parseInt(aIntStrForInit)} }会抛出 NumberFormatException:所以此时前两种情况抛出的是错误——你不能解决它而只能修改源代码区避免它 技术上来说: 一,框架或者库,不管是沙雕还是没考虑到而发生了致命错误,就该给对方抛出个 java.lang.Error,告诉对方这玩意你别解决了,等我解决或者用其它库。而不能是抛出个 java.lang.Exception 或者 java.lang.RuntimeException,告诉对方这玩意你自行解决或者不予理会。作为对方,你应该感谢对方抛了 Error 通知你,而不是抛个 Exception 掩盖致命错误。 二,前人的代码,不管是沙雕、不想干,还是什么乱起八糟的原因,发生致命错误的时候,就该给后人抛出个 java.lang.Error,告诉后人这玩意你别想在你自己的代码中解决了,重构我给你留的代码或者向上级撂挑子吧。作为后人,你应该感谢前人好心提醒你,而不是弄个 Exception 继续糊弄你。 技术上来说还可以简单的一句话:坑就在那里,你要过去就必须填坑;你把 Error 换成 Exception 并不能填坑,反而让你掉进去。 以上大概能解决你对“Never catch Throwable class”的疑问。 下面来说 static int i = Integer.parseInt("e");抛出 Error。 静态变量不是 new 对象的时候由 ClassLoader 设值的,而是 JVM 看到它的时候由 JVM 设值的,此时要是无法设值,那就是 JVM 级别的致命错误。你就算不考虑底层实现,在上层上看,给静态值设置值却设不上去,就像给人取名叫张三但“张三”这两个字有脾气一样,是致命错误。static SomeType someVariable = {一段有可能出错的代码},这是很危险的行为,抛出 Error 不足为怪。 |
15
passerbytiny 2020-01-17 11:42:46 +08:00
|
16
dallaslu 2020-01-17 11:51:39 +08:00 1
V2EX 的评论里到底能不能用 markdown 呀?
|
17
matepi OP @passerbytiny 不,并不会是想在外层解决。这个问题之所以提,是就是因为这是没法很好在外层解决的。必须改内层他人代码的“穿层”的问题。
有问题能查就行,这种异常麻烦就加之大家都不捕获 exception 以外的 throwable 来记日志。 这个 parse"e"只是个简写例子,不是真傻代码。真情况是环境上遇到过 parseInt 一个配置文件里面读上来的字符串。然后配置字符串该写数字 0 的,写了个字母 O。然后解析失败。error 异常一路跑到最外层,最外层的 logger 只捕获 exception。没有默认 err 的 log,异常在所有的日志里都没有。结果这个问题就变得异常的难查明。 |
18
lazyfighter 2020-01-17 13:11:16 +08:00
理解一下 error 和 exception 我感觉就可以明白了
|
19
matepi OP 如果大家要写类似在初始化中的代码
首先还是推荐大家搞工厂单例 再不济直接再类构造里面写个 static 变量判 null,或判默认-1 之类的,再读取之类 if (svar == -1) svar = readAndParseIntFromProp(...); 都比 直接的 static int svar = readAndParseIntFromProp(...); 要少点坑 |
20
sagaxu 2020-01-17 13:30:19 +08:00 via Android
@matepi 我经常 catch throwable,因为某些场景下,甭管什么原因失败的,我可以 fallback,fallback 失败再降级使用。catch throwable 不是洪水猛兽。
|
21
Raymon111111 2020-01-17 14:34:53 +08:00
抛出来包装了一下
试想你写一个获取用户的接口,结果数据库报了个数据库相关的错误,你肯定得转一下啊 |
22
restlessdream 2020-01-17 14:49:50 +08:00
Java 的 Error 类 doc 第一句就是:
An {@code Error} is a subclass of {@code Throwable} that indicates serious problems that a reasonable application should not try to catch. Most such errors are abnormal conditions. 如果出现 Error 的异常,这个时候就表明是严重错误,必须要进行解决,而不是通过 catch 来忽略掉,上面很多人说了,类加载期间出现异常,那么这个类就是不可用的,这个时候需要去解决代码 bug,而不是去 catch 来忽略掉。 |
23
matepi OP @restlessdream 没说抓了忽略掉不解决……说的是由于有人真去信了 Never catch Throwable class,造成异常一路到了最外层,都没人抓。导致日志里都找不到 Error 的异常。导致 bug 的难以定位解决。
|
24
chendy 2020-01-17 16:02:38 +08:00
“我同样要解决的是“抓到”这个异常之后修正。“
不知道什么类型的项目,如果是 crud 类的全局抓 Throwable 打 log 看一眼 stack trace 就能抓,然后改修修改甩锅甩锅… |
25
matepi OP @chendy 不会抓 Throwable,只会抓 Exception,是很多人的习惯。相信这楼里面都有超过一半都是。且希望写全局的人知道抓 Throwable 吧。更多时候只能在自己的代码出口上抓个 Throwable,记下日志,然后继续 throw。
|
26
mxalbert1996 2020-01-17 23:49:07 +08:00 via Android
这里 never catch 的 catch 指的是狭义的 catch,即 catch 后吞掉,先 catch 写了日志以后再 throw 算 rethrow 吧。
|