栈追踪(Stack Trace)
在本主题中,你将了解一个有助于调试应用程序的重要功能:栈追踪(Stack Trace)。它显示了应用程序中直到产生栈追踪消息为止的调用栈(Call Stack),当应用程序抛出错误时,这条信息就会在你的 IDE 中显示出来。我们将分析一个示例,并学习栈追踪消息能告诉我们什么、如何解读它。你还将学习如何在程序运行期间的任意时刻获取栈追踪。
如你所知,调用栈是一种后进先出(LIFO)的数据结构,提供了方法的执行顺序信息。它由多个 栈帧(Stack Frame) 组成,每个栈帧代表一个方法。
栈追踪详解
在学习不同类型的异常时,我们也讨论了不同的抛出异常方式。现在,是时候探索这些异常背后的消息了。请看以下示例:
import java.util.Scanner
fun main() {
val scanner = Scanner(System.`in`)
val input: String = scanner.nextLine()
val number = input.toInt() // 此处可能发生异常!
println(number + 1)
}
如果我们输入一个单词而不是数字(例如 "Kotlin"
),应用程序将抛出错误,并显示如下的栈追踪信息:
Exception in thread "main" java.lang.NumberFormatException: For input string: "Kotlin"
at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67)
at java.base/java.lang.Integer.parseInt(Integer.java:668)
at java.base/java.lang.Integer.parseInt(Integer.java:784)
at MainKt.main(Main.kt:6)
at MainKt.main(Main.kt)
栈追踪解读
我们先来看看最上面的一行,其中包含三个重要信息:
-
异常发生的线程:比如
"main"
线程是应用程序启动时创建的主线程。 -
异常类型的类:这里是
NumberFormatException
,属于java.lang
包。 -
异常消息:指出发生异常的原因(这里是输入了字符串
"Kotlin"
)。
接下来,我们看下面的几行。倒数第二行指向程序中导致异常发生的那一行代码:input.toInt()
。它调用了如下方法:
public actual inline fun String.toInt(): Int = java.lang.Integer.parseInt(this)
这个方法进一步调用了 Integer
类中的重载方法:
public static int parseInt(String s) throws NumberFormatException {
return parseInt(s, 10);
}
而在底层的 parseInt(String s, int radix)
中,我们看到抛出异常的语句如下:
if (digit < 0 || result < multmin) {
throw NumberFormatException.forInputString(s, radix);
}
异常消息的具体构建方式如下:
static NumberFormatException forInputString(String s, int radix) {
return new NumberFormatException("For input string: \"" + s + "\"" +
(radix == 10 ?
"" :
" under radix " + radix));
}
修改代码:调用另一个方法
我们稍作修改,把部分代码放到一个单独的方法中:
import java.util.*
fun main() {
val scanner = Scanner(System.`in`)
val input = scanner.nextLine()
demo(input)
}
fun demo(input: String) {
val number = input.toInt() // 此处可能发生异常!
println(number + 1)
}
这时,如果输入 "Kotlin"
,栈追踪信息将多出一行,表示调用了 demo(input)
方法:
Exception in thread "main" java.lang.NumberFormatException: For input string: "Kotlin"
at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67)
at java.base/java.lang.Integer.parseInt(Integer.java:668)
at java.base/java.lang.Integer.parseInt(Integer.java:784)
at MainKt.demo(Main.kt:10)
at MainKt.main(Main.kt:6)
at MainKt.main(Main.kt)
调用栈结构
栈追踪反映了调用栈的结构。由于调用栈是后进先出(LIFO),程序最开始执行的 main()
方法在最底部,是最后打印出来的。
主动获取栈追踪
除了抛出异常时自动生成栈追踪,你还可以主动获取栈追踪,例如通过:
for (element in Thread.currentThread().stackTrace) {
println(element)
}
这段代码获取了当前线程的栈追踪,返回的是一个 StackTraceElement
数组。你也可以使用 Throwable().stackTrace
或 Throwable().printStackTrace()
。
StackTraceElement 类简介
每个栈帧在 Kotlin 中由 StackTraceElement
表示。如果你在循环中写:
println(element.className)
输出如下:
java.lang.Thread
MainKt
MainKt
MainKt
StackTraceElement
类还提供了其他方法,如:
-
getMethodName()
:获取方法名 -
getLineNumber()
:获取代码行号
建议你查阅官方文档深入了解这个类的所有方法。
最后一个例子:主动打印栈追踪
import java.util.Scanner
fun main() {
val scanner = Scanner(System.`in`)
val input = scanner.nextLine()
demo(input)
}
fun demo(input: String) {
for (element in Thread.currentThread().stackTrace) {
println(element)
}
val number = input.toInt() // 此处可能发生异常!
println(number + 1)
}
如果输入一个数字且没有异常发生,输出类似:
java.base/java.lang.Thread.getStackTrace(Thread.java:1610)
MainKt.demo(Main.kt:10)
MainKt.main(Main.kt:6)
总结
本节你学习了栈追踪:Kotlin 提供的一个强大调试工具,帮助你了解程序的执行步骤,并快速定位错误。栈追踪初看可能令人困惑,但掌握之后,它是分析异常最有力的帮手之一。