Throwable.printStackTrace() / getStackTrace()try {
// 業務代碼
} catch (Exception e) {
// 1. 列印完整堆疊到標準錯誤流 (含行號)
e.printStackTrace();
// 2. 程式化取得 StackTraceElement 陣列
StackTraceElement[] elements = e.getStackTrace();
if (elements.length > 0) {
StackTraceElement top = elements[0]; // 棧頂 = 拋出異常的那一行
System.out.println("異常類別: " + top.getClassName());
System.out.println("異常方法: " + top.getMethodName());
System.out.println("檔案名稱: " + top.getFileName());
System.out.println("行號: " + top.getLineNumber()); // 關鍵:原始碼行號
}
}
javac -g (預設開啟) 或 javac -g:lines,vars,source。勿使用 -g:none。-keepattributes SourceFile,LineNumberTable (見 Android 混淆配置)。-1 或非預期值。可用 -XX:-Inline 關閉內聯除錯 (僅開發環境)。若 getLineNumber() 回傳 -1 或堆疊僅顯示方法名,可能原因:
LineNumberTable 屬性 (如某些舊版編譯器或特殊編譯參數)。Native Method (JNI 層拋出),Java 層無行號資訊。lambda / method reference 產生的合成類別,行號對應原始碼可能偏移。// 統一異常處理器 (Spring @ControllerAdvice / Thread.setDefaultUncaughtExceptionHandler)
public class GlobalExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
public void handle(Throwable t) {
StringWriter sw = new StringWriter();
t.printStackTrace(new PrintWriter(sw));
String stack = sw.toString();
// 取得最頂層業務代碼行 (過濾框架堆疊)
StackTraceElement[] st = t.getStackTrace();
String bizLocation = "Unknown";
for (StackTraceElement el : st) {
if (el.getClassName().startsWith("com.mycompany.")) {
bizLocation = el.getClassName() + "." + el.getMethodName() + ":" + el.getLineNumber();
break;
}
}
log.error("異常: {} | 業務定位: {} | 完整堆疊:\n{}", t.getMessage(), bizLocation, stack);
}
}
「拋出異常的那一行」正是 e.getStackTrace()[0] (或 printStackTrace() 第一行 at ...) 所指示的位置。確保編譯保留行號、混淆保留 LineNumberTable,即可在生產環境精確定位錯誤源頭。