投掷总是相同的异常实例中的Java(Throwing always the same excepti

2019-08-08 01:25发布

它总是告诉我,Java的异常处理是相当昂贵的。

我问,如果它是一个很好的做法在程序的开始,而无需创建一个新的,总是抛出相同的异常对象创建一个特定类型的异常情况。

我只是想打一个比方。 常见的代码:

if (!checkSomething(myObject))
   throw new CustomException("your object is invalid");

替代方案:

static CustomException MYEXP = new CustomException("your object is invalid");

//somewhere else
if (!checkSomething(myObject))
    throw MYEXP;

当然,我在这里做一些假设:

  1. MyCustomException没有参数
  2. 客户端的代码,只要是一个很好的做法与否,heavlily基于异常处理和重构是不是一种选择。

所以问题是:

  1. 这是一个好的做法呢?
  2. 这是否损坏一些JVM机制呢?
  3. 如果1是肯定的,有一个性能增益的可能性? (我想不会,但不知道)
  4. 如果1和3是的,为什么不赞助的做法呢?
  5. 如果1是否定的,为什么马丁·奥德斯基在他的Scala介绍说,这是斯卡拉如何工作的一些情况? (在28.30分钟他tolds那个破实施已抛出异常,观众说,这是耗时的,他回答说没有创建例外每次) 2009年FOSDEM

我希望这不是一个空闲/愚蠢的问题,我很好奇这一点。 我认为,在异常处理实际成本是处理 ,而不是创造。

在FOSDEM演示精确的讨论编辑新增参考

免责声明:没有我的代码就像提出和我无意来管理这样的例外,我只是在做一个“假设”的问题,并从affermation该视频产生的这种好奇心。 我想:如果在Scala的事,为什么不是在Java中?

Answer 1:

不,不这样做。 昂贵的部分不处理例外,它产生的堆栈跟踪。 不幸的是,堆栈跟踪也是有用的部分。 如果抛出一个异常保存你会传递一种误导堆栈跟踪。

这可能是斯卡拉的执行中有些情况下是有意义的做到这一点。 (也许他们正在做的事情递归,并希望这样的情况下,他们耗尽内存,他们仍然可以产生一个异常生成异常对象放在首位。)他们也有很多的信息,关于他们在做什么,使他们有更好的机会中得到它的权利。 但是,JVM语言实现做出优化是一个非常特殊的情况。

这样你就不会被打破任何东西,除非你想提供误导性信息构成破坏。 这似乎是一个很大的风险给我。

尝试托马斯Eding的建议,关于如何创建一个没有堆栈跟踪异常似乎工作:

groovy:000> class MyException extends Exception {
groovy:001>     public Throwable fillInStackTrace() {}}
===> true
groovy:000> e = new MyException()
===> MyException
groovy:000> Arrays.asList(e.stackTrace)
===> []

还检查了JLS :

由法爆破抛出的NullPointerException异常(这是一种RuntimeException的)不是由主try语句抓住,因为一个NullPointerException是不能分配给类型BlewIt的变量。 这导致最后子句来执行,在此之后,线程执行主,它是测试程序的唯一线程,终止因为未捕获到异常,这通常导致在打印异常名称和简单的回溯的。 然而,此规范不需要回溯。

与强制回溯问题是一个例外可以在程序中的一个点来创建并在稍后一个抛出。 它是昂贵存储栈跟踪在一个例外,除非它实际上是抛出(在这种情况下,迹线可同时展开堆栈来产生)。 因此,我们不强制要求在每一个异常的回溯。



Answer 2:

Q1。 这是一个好的做法呢?

不要在我的书。 它增加了复杂性和阻碍诊断(见我的回答Q2)。

Q2。 这是否损坏一些JVM机制呢?

你不会得到这样的异常对象有意义的堆栈跟踪。

Q3。 如果1是肯定的,有表演获得什么? (我想不会,但不知道)

Q4。 如果1和3是的,为什么不赞助的做法呢?

由于上述问题。

Q5。 如果1是否定的,为什么马丁·奥德斯基在他的Scala介绍说,这是斯卡拉如何工作的一些情况? (抱歉,我不记得这肯定此刻的上下文中)2009年FOSDEM

硬而不上下文回答。



Answer 3:

你可以这样做,但异常

  1. 必须没有堆栈跟踪,因为初始堆栈跟踪只会在后续使用混淆。

  2. 不得接受抑制异常。 如果多个线程试图抑制异常添加到它,事情会损坏。

所以,你的异常构造函数必须做

super(msg, cause, /*enableSuppression*/false, /*writableStackTrace*/false);

看到http://docs.oracle.com/javase/7/docs/api/java/lang/Throwable.html#Throwable%28java.lang.String,%20java.lang.Throwable,%20boolean,%20boolean%29


现在,是它有用吗? 是的,不然为什么会这两个布尔标志首先存在吗?:)

在一些复杂的情况下,异常可以被用作一个流量控制装置,它可以产生一个更简单,更快速的代码。 这样的异常被称为“控制异常”。

如果一个例外是真的预示着什么异常错误的程序,然后使用传统的例外。



Answer 4:

即使例外是比较昂贵的,并应保持在最低限度,他们没有花费这么多,你应该“为提高性能的目的”做钝的事情这是经常不好的借口,它甚至被一些不成熟的优化应该考虑不惜一切代价避免。 虽然这是不完全正确,你可以测量异常有多慢的。

long start = System.nanoTime();
int exceptionCount = 0;
for (int i = 0; i < 20000; i++)
    try {
        int j = i / (i & 1);
    } catch (ArithmeticException ae) {
        exceptionCount++;
    }
long time = System.nanoTime() - start;
System.out.printf("Each exception took average of %,d ns%n", time / exceptionCount);

打印什么,我相信这是一个合理的估计。

Each exception took average of 3,064 ns

注:为循环数量的增加,唯一的例外是优化掉。 即对于10倍的迭代

Each exception took average of 327 ns

和10倍多

Each exception took average of 35 ns

和10倍多

Each exception took average of 5 ns

如果异常抛出不够,它出现在JIT是足够聪明的优化异常了。



文章来源: Throwing always the same exception instance in Java