問題背景
C/C++ 可用 __func__ 或 __FUNCTION__ 直接獲取函數名。Java 中一切皆物件,需從堆疊追蹤物件獲取。
方法一:Thread.currentThread().getStackTrace()
public class MethodNameDemo {
public static void main(String[] args) {
printMethodName();
}
static void printMethodName() {
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
// stack[0] = getStackTrace()
// stack[1] = printMethodName()
// stack[2] = main()
StackTraceElement caller = stack[2]; // 視調用深度調整索引
System.out.println("方法名: " + caller.getMethodName());
System.out.println("類名: " + caller.getClassName());
System.out.println("檔案: " + caller.getFileName());
System.out.println("行號: " + caller.getLineNumber());
}
}
輸出
方法名: main
類名: MethodNameDemo
檔案: MethodNameDemo.java
行號: 10
方法二:new Throwable().getStackTrace()
public class MethodNameDemo2 {
public static void main(String[] args) {
printMethodName();
}
static void printMethodName() {
StackTraceElement[] stack = new Throwable().getStackTrace();
// stack[0] = printMethodName()
// stack[1] = main()
StackTraceElement current = stack[0];
StackTraceElement caller = stack[1];
System.out.println("當前方法: " + current.getMethodName());
System.out.println("調用者方法: " + caller.getMethodName());
System.out.println("調用者類名: " + caller.getClassName());
}
}
輸出
當前方法: printMethodName
調用者方法: main
調用者類名: MethodNameDemo2
關鍵差異:索引位置
| 方法 | stack[0] | stack[1] | 適用場景 |
| Thread.getStackTrace() | getStackTrace() 呼叫點 | 當前方法 | 需跳過 1-2 層獲取調用者 |
| new Throwable() | 當前方法 | 調用者方法 | 直接獲取當前/調用者 |
索引不固定的原因
- JDK 版本差異
- JIT 編譯器內聯優化可能移除堆疊幀
- Lambda/方法引用產生合成方法
- 異步執行緒堆疊不同
健壯的工具方法
public class StackTraceUtil {
/** 獲取調用者資訊(跳過工具方法本身) */
public static StackTraceElement getCaller(int skipFrames) {
StackTraceElement[] stack = new Throwable().getStackTrace();
int index = 1 + skipFrames; // stack[0]=getCaller, stack[1]=直接調用者
if (index < stack.length) return stack[index];
return null;
}
/** 獲取當前方法名 */
public static String getCurrentMethodName() {
return new Throwable().getStackTrace()[0].getMethodName();
}
/** 獲取調用者方法名 */
public static String getCallerMethodName() {
return new Throwable().getStackTrace()[1].getMethodName();
}
/** 獲取調用者類名 */
public static String getCallerClassName() {
return new Throwable().getStackTrace()[1].getClassName();
}
/** 列印完整堆疊(除錯用) */
public static void printStackTrace() {
for (StackTraceElement e : new Throwable().getStackTrace()) {
System.out.println(e);
}
}
}
// 使用範例
class Service {
public void doBusiness() {
logEntry();
// 業務邏輯...
logExit();
}
void logEntry() {
StackTraceElement caller = StackTraceUtil.getCaller(1); // 跳過 logEntry
System.out.printf("[ENTER] %s.%s()%n", caller.getClassName(), caller.getMethodName());
}
void logExit() {
StackTraceElement caller = StackTraceUtil.getCaller(1);
System.out.printf("[EXIT] %s.%s()%n", caller.getClassName(), caller.getMethodName());
}
}
效能考量
- 建立 Throwable 物件較重:涉及堆疊快照捕獲,頻繁呼叫影響效能
- 避免在熱路徑使用:高頻迴圈、遞迴、即時系統
- 替代方案:
// 編譯期常數(Java 9+ StackWalker API)
private static final StackWalker WALKER = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
public static String getCurrentMethodNameFast() {
return WALKER.getCallerClass().getSimpleName(); // 需配合 getMethodName 邏輯
}
// 或使用 AOP/編譯期插桩(如 Log4j2、Spring AOP)
Java 9+ StackWalker API(推薦現代專案)
import java.lang.StackWalker.StackFrame;
public class ModernStackTrace {
private static final StackWalker WALKER = StackWalker.getInstance(
StackWalker.Option.RETAIN_CLASS_REFERENCE
);
public static void demo() {
WALKER.walk(frames -> frames
.skip(1) // 跳過 walk 呼叫
.findFirst()
.ifPresent(frame -> {
System.out.println("類名: " + frame.getClassName());
System.out.println("方法名: " + frame.getMethodName());
System.out.println("行號: " + frame.getLineNumber());
}));
}
}
應用場景
| 場景 | 用途 | 注意 |
| 日誌框架 | 自動記錄呼叫位置 | 使用 StackWalker 或編譯期插桩 |
| 除錯輔助 | 印出呼叫鏈路 | 開發階段可接受效能開銷 |
| 安全審計 | 記錄敏感操作發起者 | 確保堆疊不可竄改 |
| 測試框架 | 標記測試方法來源 | JUnit 內建支援 |
結語
Java 獲取方法名依賴堆疊追蹤,new Throwable().getStackTrace()[0] 取當前方法,[1] 取調用者;Thread.getStackTrace() 索引偏移 1。索引非固定,建議封裝工具類並測試驗證。現代專案(Java 9+)優先使用 StackWalker API,效能更好且 API 更豐富。高頻場景避免直接使用,改用 AOP 或編譯期方案。
- 上一篇文章: HttpClient 重定向後獲取最終 URL:HttpContext 捕獲目標主機與實際請求 URI
- 下一篇文章: Java 實作 FTP 檔案下載完整範例與最佳實踐
Copyright ©
程式碼之家 All Rights Reserved