Java代码中如何获文件名和行号等源码信息?

时间:2020-11-30 15:36:39

C语言中有__FILE__、__LINE__等预定义宏,用于获取当前文件名和行号等信息,而且它们的值在预处理时就已经确定了,不会占用运行时时间去计算,这对打印日志相当有用。那么,Java语言是否也有类似的功能呢?

Java是否提供某种方法:可以让用户代码在编译时确定源码行号等信息,本人暂时不知晓。不过从网上搜索得到的方法大致是:

  • Thread.currentThread().getStackTrace()[1].getFileName():获取当前文件名;
  • Thread.currentThread().getStackTrace()[1].getLineNumber():获取当前行号。

其中:Thread.currentThread().getStackTrace()返回的是一个数组形式的函数调用栈(栈顶在索引0处),其中第1个元素(索引为0)为最新调用的函数信息(getStackTrace()),第2个元素(索引为1)为当前函数(即调用getStackTrace()的函数)信息。示例:

 1 public class Test {
 2     public static void main(String args[]) {
 3         StackTraceElement[] stack = Thread.currentThread().getStackTrace();
 4         for (int i = 0; i < stack.length; ++i)
 5             System.out.println(stack[i].getFileName() + ":" + stack[i].getLineNumber() + ": " + stack[i].getMethodName());
 6         System.out.println("");
 7         foo();
 8     }
 9     static void foo() {
10         StackTraceElement[] stack = Thread.currentThread().getStackTrace();
11         for (int i = 0; i < stack.length; ++i)
12             System.out.println(stack[i].getFileName() + ":" + stack[i].getLineNumber() + ": " + stack[i].getMethodName());
13         System.out.println("");
14         bar();
15     }
16     static void bar() {
17         StackTraceElement[] stack = Thread.currentThread().getStackTrace();
18         for (int i = 0; i < stack.length; ++i)
19             System.out.println(stack[i].getFileName() + ":" + stack[i].getLineNumber() + ": " + stack[i].getMethodName());
20     }
21 }

输出:

$ javac Test.java
$ java Test
null:-1: getStackTrace
Test.java:3: main

null:-1: getStackTrace
Test.java:10: foo
Test.java:7: main

null:-1: getStackTrace
Test.java:17: bar
Test.java:14: foo
Test.java:7: main

于是,如果我们想在日志中打印当前文件名和行号,就可以:

 1 public class Test {
 2     public static void main(String args[]) {
 3         System.out.println(Thread.currentThread().getStackTrace()[1].getFileName() + ":" + Thread.currentThread().getStackTrace()[1].getLineNumber());
 4         foo();
 5     }
 6     static void foo() {
 7         System.out.println(Thread.currentThread().getStackTrace()[1].getFileName() + ":" + Thread.currentThread().getStackTrace()[1].getLineNumber());
 8         bar();
 9     }
10     static void bar() {
11         System.out.println(Thread.currentThread().getStackTrace()[1].getFileName() + ":" + Thread.currentThread().getStackTrace()[1].getLineNumber());
12     }
13 }

输出:

$ javac Test.java
$ java Test
Test.java:3
Test.java:7
Test.java:11

不过每次都要写这么长的几串代码“Thread.currentThread().getStackTrace()[1].getXXX()”拼一起看起来难看敲起来也累。于是我们可以将getStackTrace()封装到另一个函数中:

 1 public class Test {
 2     public static void main(String args[]) {
 3         System.out.println(__FILE__() + ":" + __LINE__());
 4         foo();
 5     }
 6     static void foo() {
 7         System.out.println(__FILE__() + ":" + __LINE__());
 8         bar();
 9     }
10     static void bar() {
11         System.out.println(__FILE__() + ":" + __LINE__());
12     }
13     static String __FILE__() {
14         return Thread.currentThread().getStackTrace()[2].getFileName();
15     }
16     static int __LINE__() {
17         return Thread.currentThread().getStackTrace()[2].getLineNumber();
18     }
19 }

输出结果一样:

$ javac Test.java
$ java Test
Test.java:3
Test.java:7
Test.java:11

参考:

  1. http://*.com/questions/22677815/eclipse-println-line-does-such-a-preprocessor-macro-exist-to-program
  2. http://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html#getStackTrace--
  3. http://docs.oracle.com/javase/8/docs/api/java/lang/StackTraceElement.html
  4. http://*.com/questions/115008/how-can-we-print-line-numbers-to-the-log-in-java
  5. http://www.codeceo.com/article/8-java-log-framework.html