为什么我可以在Java中“伪造”exception的堆栈跟踪?

如果我运行以下测试,它将失败:

public class CrazyExceptions { private Exception exception; @Before public void setUp(){ exception = new Exception(); } @Test public void stackTraceMentionsTheLocationWhereTheExceptionWasThrown(){ String thisMethod = new Exception().getStackTrace()[0].getMethodName(); try { throw exception; } catch(Exception e) { assertEquals(thisMethod, e.getStackTrace()[0].getMethodName()); } } } 

出现以下错误:

 Expected :stackTraceMentionsTheLocationWhereTheExceptionWasThrown Actual :setUp 

堆栈跟踪只是平躺着。

抛出exception时,为什么不重写堆栈跟踪? 我不是Java开发者,也许我在这里遗漏了一些东西。

实例化exception时创建堆栈跟踪,而不是在抛出exception时创建堆栈跟踪。 这是Java语言规范的指定行为

 20.22.1 public Throwable() This constructor initializes a newly created Throwable object with null as its error message string. Also, the method fillInStackTrace (§20.22.5) is called for this object. .... 20.22.5 public Throwable fillInStackTrace() This method records within this Throwable object information about the current state of the stack frames for the current thread. 

我不知道为什么他们这样做,但如果规范定义它,它至少在所有各种Java VM上都是一致的。

但是,您可以通过手动调用exception.fillInStackTrace()来刷新它。

另请注意,您应该使用Thread.currentThread().getStackTrace()而不是使用new Exception().getStackTrace() (错误的样式)。

exception的堆栈跟踪在exception的创建时填充。 否则就不可能捕获exception,处理它并重新抛出它 。 原始的堆栈跟踪会丢失。

如果要强制执行此操作,则必须显式调用exception.fillInStackTrace()

因为您没有要求重写该堆栈跟踪。 它是在setUp方法中创建时设置的,您从未做过任何改变它的操作。

Exception类没有给你任何设置方法名称的机会; 它是不可改变的。 所以我不知道你可以在哪里重新设置方法名称,除非你想采取像reflection一样令人发指的事情。

你的@Test注释没有告诉我你是使用JUnit还是TestNG,因为我看不到静态导入,但在任何一种情况下你都可以通过使用“预期”运行测试以查看是否抛出了特定exception“@Test注释中的成员。

您不希望抛出exception来更改堆栈跟踪,或者您无法安全地重新抛出exception。

 public void throwsException() { throw new RuntimeException(); } public void logsException() { try { throwsException(); } catch (RuntimeException e) { e.printStrackTrace(); throw e; // doesn't alter the exception. } } @Test public void youCanSeeTheCauseOfAnException(){ try { logsException(); } catch(Exception e) { e.printStrackTrace(); // shows you the case of the exception, not where it was last re-thrown. } } 

我认为假设您不会实例化exception,除非您正在抛出exception,那么为什么要付出代价才能获得两次堆栈跟踪?

抛出它时很难重新创建堆栈跟踪,因为这只是将对象发送出去。

应该在throw之前完全设置exception,因此实例化的一部分是获取堆栈跟踪。

更新:

您可以调用fillInStackTrace来解决此问题: http : //java.sun.com/j2se/1.4.2/docs/api/java/lang/Throwable.html#fillInStackTrace%28%29

exception中的堆栈跟踪对应于“新”操作,没有别的。