如何使用Java读取Java文件结构?

4
我尝试读取一个Java文件并在控制台中显示包、类和方法名。例如:
文件: Test.java
package tspec.test;

public class Test {
   public void addTest () {}
   public void deleteTest () {}
}

输出:

package name: tspec.test
class name: Test
method name:
addTest
deleteTest

提前感谢您:)


当你说“读取Java文件”时,你是指Java源代码文件(.java)还是Java字节码文件(.class)? - Bert F
我猜你可以查看javac的源代码,看看它如何解析.java文件。你这样做是为了练习,还是作为某个更大项目的一部分? - Iain Samuel McLean Elder
5个回答

6
这可以通过使用Java编译器API(从Java 6开始引入)来实现。不幸的是,这个解决方案仅限于Sun的JDK。因此,您必须安装该JDK,并将其tools.jar文件包含在您的类路径中。
public void displayInformation(File javaSourceFile) throws Exception {
    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

    // The file manager locates your Java source file for the compiler. Null arguments indicate I am comfortable with its default behavior.
    StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);

    // These will be parsed by the compiler
    Iterable<? extends JavaFileObject> fileObjects = fileManager.getJavaFileObjects(javaSourceFile);

    // Creates a new compilation task. This doesn't actually start the compilation process.
    // Null arguments indicate I am comfortable with its default behavior.
    CompilationTask task = compiler.getTask(null, null, null, null, null, fileObjects);

    // Cast to the Sun-specific CompilationTask.
    com.sun.tools.javac.api.JavacTaskImpl javacTask = (com.sun.tools.javac.api.JavacTaskImpl) task;

    // The Sun-specific JavacTaskImpl can parse the source file without compiling it, returning 
    // one CompilationUnitTree for each JavaFileObject given to the compiler.getTask call (only one in our case).
    Iterable<? extends CompilationUnitTree> trees = javacTask.parse();
    CompilationUnitTree tree = trees.iterator().next();

    // Create a class that implements the com.sun.source.tree.TreeVisitor interface.
    // The com.sun.source.util.TreeScanner is a good choice because it already implements most of the logic.
    // We just override the methods we're interested in.
    class MyTreeVisitor extends TreeScanner<Void, Void> {

        @Override
        public Void visitClass(ClassTree classTree, Void p) {
            System.out.println("class name: " + classTree.getSimpleName());
            System.out.println("method name:");
            return super.visitClass(classTree, p);
        }

        @Override
        public Void visitMethod(MethodTree methodTree, Void p) {
            System.out.println(methodTree.getName());
            return super.visitMethod(methodTree, p);
        }

    }

    tree.accept(new MyTreeVisitor(), null);
}

当我传递一个内容为你的样例的File给这个方法时,我得到了如下输出:

类名: Test
方法名:
addTest
deleteTest

不幸的是,我还没有找出包名存储在哪里。


使用"@Override public Void visitCompilationUnit( CompilationUnitTree unit, Void arg ) { System.out.println( "Package: " + unit.getPackageName() ); return super.visitCompilationUnit( unit, arg ); };"来获取包名。 - Christian Ullenboom

3

它的目的是内省Java代码并报告其内容。使用反射可以做到以下事情:

 Class.forName(className).getDeclaredMethods();

这两种解决方案都不需要第三方库或工具。


0

你不需要自己解析Java文件来完成这个任务!Java已经包含了一种获取有关其自身类、方法和包信息的方法:它被称为反射

看一下java.lang.Class类。该类的每个实例表示特定的Java类,并包含返回类名、所在包、包含的方法以及许多其他信息的方法。

值得注意的还有java.lang.reflect包,因为Class的某些方法返回此包中的类型。该包包含用于表示方法、类型、字段等内容的类。

要获取Test类的Class实例,可以使用以下代码:

Class<?> testclass = Class.forName("tspec.test.Test");

这将返回一个未知类型的类,如果您不熟悉泛型,则尖括号内的问号表示的就是这个意思。类实例的类型为什么是未知的呢?因为您使用字符串指定了类名,在运行时进行解析。在编译时,Java 无法确定传递给 forName 的字符串是否代表一个有效的类。

然而,如上所定义的 testclass 将适用于获取类的名称、方法和包含的包。


4
我认为海报可以访问一个 .java 文件而不是一个 .class 文件。 - Binil Thomas

0

唯一的困难在于Java代码可能格式不良好,例如函数声明可以分散在多行上。

最终的解决方案是创建一个自动机来对源代码进行标记化处理,然后应用一些编译器技术从解析数据中获取所需内容。


正则表达式可以处理多行,但是我认为最终的解决方案应该是一个解析器。 - Marcus Adams

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接