📜  打印行号java(1)

📅  最后修改于: 2023-12-03 15:39:42.164000             🧑  作者: Mango

在Java中打印行号

在开发中,我们可能需要输出某些代码行的行号,以便于查找或调试。Java提供了几种方式来打印行号,下面我们将介绍几种常见的方式。

方法一:使用行号枚举

Java中的每一行都被编译成字节码指令。我们可以利用这个特性,通过遍历字节码来获取行号。具体步骤如下:

  1. 编译Java源文件时设置编译选项,以便生成行号信息。例如,在命令行中使用 javac -g 命令编译。

  2. 在Java代码中调用 Thread.currentThread().getStackTrace() 方法来获取当前线程调用栈信息。

  3. 遍历调用栈,找到调用当前方法的第一个用户代码类的类名。

  4. 使用 Class.getResourceAsStream() 方法加载该类的字节码。

  5. 使用 ClassReaderLineNumberTable 类来解析字节码,获取行号信息。

下面是一段示例代码:

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.LineNumberNode;
import org.objectweb.asm.tree.MethodNode;

import java.io.IOException;
import java.io.InputStream;

public class LineNumberPrinter {
    public static void printLineNumber() throws IOException {
        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
        String className = null;
        for (StackTraceElement element : stackTrace) {
            String elementClassName = element.getClassName();
            if (!elementClassName.startsWith("java.") && !elementClassName.startsWith("sun.")) {
                className = elementClassName;
                break;
            }
        }
        if (className == null) {
            throw new IllegalStateException("Cannot find user code class");
        }
        String classFileName = className.replace('.', '/') + ".class";
        InputStream classInputStream = LineNumberPrinter.class.getClassLoader().getResourceAsStream(classFileName);
        ClassReader classReader = new ClassReader(classInputStream);
        LineNumberClassVisitor classVisitor = new LineNumberClassVisitor();
        classReader.accept(classVisitor, 0);
        int lineNumber = classVisitor.getLineNumber();
        System.out.println("Line number: " + lineNumber);
    }

    private static class LineNumberClassVisitor extends ClassVisitor {
        private int lineNumber;

        public LineNumberClassVisitor() {
            super(Opcodes.ASM5);
        }

        @Override
        public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
            return new LineNumberMethodVisitor();
        }

        public int getLineNumber() {
            return lineNumber;
        }

        private class LineNumberMethodVisitor extends MethodNode {
            @Override
            public void visitLineNumber(int line, Label start) {
                super.visitLineNumber(line, start);
                lineNumber = line;
            }
        }
    }
}

在示例代码中,LineNumberPrinter.printLineNumber() 方法将会输出其所在行的行号。

注意:上面这种方法比较复杂,也不太实用,只作为一种思路的参考。在实际开发中,我们更多地使用下面两种方法。

方法二:使用行数常量

在Java源文件中,可以使用行数常量来指定某行代码所在的行号。例如,在下面的代码中:

1 public class Main {
2     public static void main(String[] args) {
3         System.out.println("Hello, world!"); // Line 3
4     }
5 }

第3行代码使用 // Line 3 注释来指定该行的行号。我们可以编写一个工具类来获取该注释所在的行号。具体步骤如下:

  1. 在Java代码中编写以下工具类:
public class LineNumberUtils {
    public static int getLineNumber() {
        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
        int lineNumber = -1;
        for (StackTraceElement element : stackTrace) {
            String fileName = element.getFileName();
            int line = element.getLineNumber();
            if (fileName != null && fileName.endsWith(".java") && line != -1) {
                String className = element.getClassName();
                String methodName = element.getMethodName();
                try {
                    Class<?> clazz = Class.forName(className);
                    Method method = clazz.getMethod(methodName);
                    String code = IOUtils.toString(method.getAnnotation(SourceCode.class).value());
                    String[] lines = code.split("(\r\n)|(\r)|(\n)");
                    lineNumber = line - 1;
                    for (int i = 0; i < lineNumber; i++) {
                        lineNumber -= lines[i].length() + System.lineSeparator().length();
                    }
                    break;
                } catch (ClassNotFoundException | NoSuchMethodException | IOException e) {
                    return -1;
                }
            }
        }
        return lineNumber;
    }
}

该工具类使用 Thread.currentThread().getStackTrace() 方法获取当前线程调用栈信息,然后遍历调用栈,找到用户代码所在的类和方法。

  1. 在需要获取行号的代码行上方加上 @SourceCode 注解,如下所示:
1 import java.io.IOException;
2 
3 public class Main {
4     @SourceCode("src/main/java/com/example/Main.java")
5     public static void main(String[] args) throws IOException {
6         System.out.println("Hello, world!"); // Line 6
7     }
8 }

其中,注解参数为代码文件的相对或绝对路径。我们可以利用注解参数来读取代码文件的内容,然后按行遍历,计算所需的行号。

  1. 在代码中调用 LineNumberUtils.getLineNumber() 方法来获取行号,如下所示:
int lineNumber = LineNumberUtils.getLineNumber();
System.out.println("Line number: " + lineNumber);

需要注意的是,该方法返回的是指定代码行的前一行行号,因为我们在代码注释中指定的是代码所在的行号而不是注释所在的行号。

方法三:使用调试器

Java提供了集成开发环境(IDE)来编写和调试代码。我们可以使用调试器来动态查看代码行号。具体步骤如下:

  1. 在代码行左侧单击鼠标,添加断点。

  2. 在IDE的调试菜单中选择启动调试器。

  3. 当程序执行到断点时,调试器会停下来。此时,我们可以查看当前代码行号等调试信息。

需要注意的是,该方法依赖于集成开发环境,不太适用于其他开发环境或生产环境。但是,在开发调试中,使用调试器是非常方便和实用的。