本文由
外刊IT评论
组织翻译
Core Java Technologies Tech Tips
Compiling with the Java Compiler API
调用Java编译器API编译Java
从第一天开始,标准Java平台就缺少能够被调用,去产生Java字节码的编译器接口. 使用Sun实现的平台,一个用户可以通过非标准的 com.sun.tools.javac
包中的Main
class 去编译你的代码 (你可以在lib子目录下的 tools.jar
文件里找到它). 然而这个包并没有提供一个标准的公开的编程接口. 使用其它实现的用户必然不能访问这个类. 使用Java SE 6和在JSR-199中定义的它的新的Java编译器接口,你可以从你自己的应用程序里访问javac编译工具了.
有两种方式使用这种工具. 一种是简单的,一种是稍微复杂点但拥有更多选项的. 你首先将会用较简单的一种去编译 "Hello, World"程序,就是下面的这个:
public class Hello {
public static void main(String args[]) {
System.out.println("Hello, World");
}
}
|
要想从Java程序里调用Java编译器,你需要访问JavaCompiler
接口. 除此外,通过访问这个接口,你可以设置源代码的路径,classpath,和目标目录. 通过指定可编译的文件为 JavaFileObject
instance ,你可以将它们全部编译. 然而,你并不需要对 JavaFileObject
了解多少.
可以使用 ToolProvider
类去请求 theJavaCompiler
接口的缺省实现. 这个 ToolProvider
类提供了一个 getSystemJavaCompiler()
方法, 它返回一个 JavaCompiler
接口的实例.
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
|
使用 JavaCompiler
运行编译最简单的方法是调用在这个接口工具里定义的 run()
方法,它的实现是:
int run(InputStream in,
OutputStream out,
OutputStream err,
String... arguments)
|
分别为前三个缺省参数 System.in
, System.out
, and System.err
传入 null
值. 参数集 String
对象表示着传入编译器的文件的名称.
这样,你应该像下面这样去编译前面显示的 Hello
源程序:
int results = tool.run(null, null, null, "Hello.java");
|
假设没有编译错误,这样会在目标目录里产生一个 Hello.class文件.
如果这里有错误, run()
方法会把它输出到标准错误输出流里,也就是 run()
方法的的第三个参数.
当错误发生时这个方法返回一个非0的结果.
你可以使用下面的代码去编译 Hello.java
源文件:
import java.io.*;
import javax.tools.*;
public class CompileIt {
public static void main(String args[]) throws IOException {
JavaCompiler compiler =
ToolProvider.getSystemJavaCompiler();
int results = compiler.run(
null, null, null, "Hello.java");
System.out.println("Result code: " + results);
}
}
|
一旦你编译了 CompileIt
一次 ,你就可以多次运行它,当你修改了 Hello.java
源程序时或者要重新编译它,你不需要重新编译 CompileIt
. 如果没有错误,运行 CompileIt
会产生下面的输出:
> java CompileIt
Result code: 0
|
运行 CompileIt
同样也会在相同的目录下产生一个 Hello.class
文件:
> ls
CompileIt.class
CompileIt.java
Hello.class
Hello.java
|
你可以完事了,因为这样使用标准编译器已经足够了,可是这还有更有用的. 当你需要更好的处理这些结果时,你可以使用第二种方法来访问编译器. 更特别的是,这第二种方式允许开发者将编译输出结果用一种更有意义的方式表现出来,而不是简单的那种送往stdeer的错误文本. 利用 StandardJavaFileManager
类我们有这种更好的途径使用编译器. 这个文件管理器提供了一种方式,用来处理普通文件的输入输出操作. 它同时利用 DiagnosticListener
实例来报告调试信息. 你需要使用的 DiagnosticCollector
类其实是监听器的一种实现.
在搞清楚你需要编译什么之前,你需要一个文件管理器. 生成一个管理器基本上需要两步: 创建一个 DiagnosticCollector
和 使用 JavaCompiler
的 getStandardFileManager()
方法获得一个文件管理器. 把 DiagnosticListener
对象传入 getStandardFileManager()
方法中. 这个监听器可以报告一些非致命的问题,到后来你可以选择性的通过把它传入 getTask()
方法来和编译器共享.
DiagnosticCollector<JavaFileObject> diagnostics =
new DiagnosticCollector<JavaFileObject>();
StandardJavaFileManager fileManager =
compiler.getStandardFileManager(diagnostics, aLocale, aCharset);
|
你也可以往这个调用里传入一个 null 值的诊断器,但这样也就等于用以前的编译器方法了.
在详细查看 StandardJavaFileManager
之前 ,编译过程涉及到 JavaCompiler
的一个方法叫做 getTask()
. 它有六个参数,返回一个叫做 CompilationTask
内部类的实例:
JavaCompiler.CompilationTask getTask(
Writer out,
JavaFileManager fileManager,
DiagnosticListener<? super JavaFileObject> diagnosticListener,
Iterable<String> options,
Iterable<String> classes,
Iterable<? extends JavaFileObject> compilationUnits)
|
缺省情况下,大部分它的参数可以是 null.
* out: System.err
* fileManager:
compiler's standard file manager
* diagnosticListener:
compiler's default behavior
* options: no command-line
options to compiler
* classes: no class
names for annotation processing
最后一个参数 compilationUnits
却是不能够为null ,因为它是你要去编译的东西. 它把我们又带回了 StandardJavaFileManager
类.注意这个参数类型: Iterable<?
extends JavaFileObject>
. StandardJavaFileManager
有两个方法返回这样的结果. 你可以使用一个文件对象的List或者 String
对象的List,用它们来表示文件名:
Iterable<? extends JavaFileObject> getJavaFileObjectsFromFiles(
Iterable<? extends File> files)
Iterable<? extends JavaFileObject> getJavaFileObjectsFromStrings(
Iterable<String> names)
|
并不仅仅 List
,实际上,任何一个能够标识需要编译的内容的集合的 Iterable
都可以. List
出现在这里只是因为它容易生成:
String[] filenames = ...;
Iterable<? extends JavaFileObject> compilationUnits =
fileManager.getJavaFileObjectsFromFiles(Arrays.asList(filenames));
|
现在你有了编译源文件的所有的必要的信息. 从 getTask(
) 返回的 JavaCompiler.CompilationTask
实现了 Callable
.接口 这样,想让任务开始就去调用call()方法.
JavaCompiler.CompilationTask task =
compiler.getTask(null, fileManager, null, null, null, compilationUnits);
Boolean success = task.call();
|
如果没有编译警告和错误,这个call() 方法会编译所有的 compilationUnits
变量指定的文件,以及有依赖关系的可编译的文件. 想要知道是否所有的都成功了,去查看一下返回的 Boolean
值. 只有当所有的编译单元都执行成功了,这个 call()
方法才返回 Boolean.TRUE
. 一旦有任何错误,这个方法就会返回 Boolean.FALSE
.
在展示运行这个例子之前,让我们添加最后一个东西,DiagnosticListener
, 或者更确切的说, DiagnosticCollector
.的实现类.把这个监听器当作getTask()的第三个参数传递进去,你就可以在编译之后进行一些调式信息的查询了.
for (Diagnostic diagnostic : diagnostics.getDiagnostics()) {
System.console().printf(
"Code: %s%n" +
"Kind: %s%n" +
"Position: %s%n" +
"Start Position: %s%n" +
"End Position: %s%n" +
"Source: %s%n" +
"Message: %s%n",
diagnostic.getCode(), diagnostic.getKind(),
diagnostic.getPosition(), diagnostic.getStartPosition(),
diagnostic.getEndPosition(), diagnostic.getSource(),
diagnostic.getMessage(null));
}
|
在最后,你应该调用管理器的close()
方法.
把所有的放在一起,就得到的了下面的程序,让我们重新编译Hello类.
import java.io.*;
import java.util.*;
import javax.tools.*;
public class BigCompile {
public static void main(String args[]) throws IOException {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
DiagnosticCollector<JavaFileObject> diagnostics =
new DiagnosticCollector<JavaFileObject>();
StandardJavaFileManager fileManager =
compiler.getStandardFileManager(diagnostics, null, null);
Iterable<? extends JavaFileObject> compilationUnits =
fileManager.getJavaFileObjectsFromStrings(Arrays.asList("Hello.java"));
JavaCompiler.CompilationTask task = compiler.getTask(
null, fileManager, diagnostics, null, null, compilationUnits);
Boolean success = task.call();
for (Diagnostic diagnostic : diagnostics.getDiagnostics()) {
System.console().printf(
"Code: %s%n" +
"Kind: %s%n" +
"Position: %s%n" +
"Start Position: %s%n" +
"End Position: %s%n" +
"Source: %s%n" +
"Message: %s%n",
diagnostic.getCode(), diagnostic.getKind(),
diagnostic.getPosition(), diagnostic.getStartPosition(),
diagnostic.getEndPosition(), diagnostic.getSource(),
diagnostic.getMessage(null));
}
fileManager.close();
System.out.println("Success: " + success);
}
}
|
编译和运行这个程序会输出成功的信息:
> javac BigCompile.java
> java BigCompile
Success: true
|
然而,如果你把 println
方法改成书写错误的 pritnln
方法,当你运行时你会得到下面的信息:
> java BigCompile
Code: compiler.err.cant.resolve.location
Kind: ERROR
Position: 80
Start Position: 70
End Position: 88
Source: Hello.java
Message: Hello.java:3: cannot find symbol
symbol : method pritnln(java.lang.String)
location: class java.io.PrintStream
Success: false
|
使用Compiler API,你可以实现比在这篇简要的提示介绍的更多的事情. 例如,你可以控制输入输出的目录或者在集成编译器里高亮一些编译错误. 现在,向 Java Compiler API表示感谢,你可以使用标准API了. For more information on the Java Compiler API and JSR 199, see the JSR 199 specification.
分享到:
相关推荐
总结一下,Java编译器API提供了在运行时编译Java源代码的能力,通过实现`JavaFileObject`接口,我们可以自定义源代码的来源,不再局限于文件。`JavaCompiler`和`StandardJavaFileManager`是核心组件,它们负责管理和...
在手机上使用Java编译器,意味着开发者可以在移动设备上直接编写、编译和测试Java程序,这对于移动应用开发来说具有很大的便利性。手机Java编译器通常会集成开发环境(IDE),提供友好的用户界面,方便程序员进行...
- **配置环境**: 在使用JNI时,需要设置JAVA_HOME和PATH环境变量,确保Java编译器和运行时环境可找到。此外,还需要配置CLASSPATH,包含JNI头文件和本地库的路径。 - **JNI编程流程**: 1. 创建Java类声明native...
描述中提到的Groovy调用Java的过程是“无缝”的,这意味着Groovy对Java API的集成非常紧密,使得开发者可以自然地在Groovy中使用Java的功能。 Java调用Groovy类则稍微复杂一些,通常有两种主要方法: 1. **方法1:...
使用Java编译器(javac)将`.java`源文件编译成`.class`字节码文件。 3. **创建C头文件**: 使用Java的`javah`工具生成C头文件(`.h`文件)。这个头文件包含了C代码中需要实现的函数原型,如: ``` javah -jni ...
Java 6及以后的版本引入了`javax.tools.JavaCompiler`接口,它提供了一套API用于在运行时编译Java源码。首先,我们需要获取`ToolProvider`的实例,然后使用`getSystemJavaCompiler()`方法来获取`JavaCompiler`实例...
使用Java编译器`javac`将Java源代码编译成字节码。例如: ```bash javac NativeExample.java ``` 3. **创建C头文件**: 使用Java的`javah`工具,根据Java类生成对应的C头文件(.h文件),该文件包含了JNI函数...
Java编译器API是Java平台标准版(Java SE)的一部分,它允许开发者在运行时动态地编译Java源代码。`CompilerUser`库是专为简化这个过程而设计的,尤其适用于那些需要在应用程序中利用Java编译器API进行源代码分析、...
这个API允许我们在程序中调用JDK的javac编译器,而无需手动启动外部进程。这在诸如元编程、动态代理或自定义构建系统等场景中非常有用。 要使用JavaCompiler,首先需要获取`ToolProvider`的实例,这是访问所有工具...
5. **动态类型**:引入了`java.lang.invoke.MethodHandle`和`java.lang.invoke.MethodHandles`等类,支持更灵活的运行时方法调用,为元编程提供了基础。 其次,Java 1.7 API 文档详细解释了核心库中的关键类和接口...
Java6动态编译案例主要涉及的是Java平台中的Java编译器API(Java Compiler API),这是Java SE 6引入的一个重要特性,允许程序在运行时动态地编译源代码。这个功能使得开发者能够在运行的应用程序中生成和编译Java类...
对于C++调用Java而言,需要通过JNI API创建并启动一个Java虚拟机实例,然后通过这个实例访问Java类和方法。 #### 示例:C++调用Java方法 考虑一个简单的示例,其中C++调用一个Java类`WinFile`的`GetFilesFromDir`...
JDK包含Java编译器(javac)、Java虚拟机(JVM)、Java运行时环境(JRE)以及一系列的工具和API文档。 2. **API(Application Programming Interface)** API是一系列预先定义的函数,允许开发者使用特定的编程...
最后,将C++代码编译成DLL,再由Java程序加载和调用。这一系列步骤对不熟悉C++的Java开发者来说,无疑增加了开发的难度。 "JAWIN",全称Java and Windows Integration Library,就是为了简化这个过程而出现的一种...
Java API文档是Java开发者不可或缺的参考资料,它详细地介绍了Java平台标准版(Java SE)的各种类库、接口和实现。这个“Java API文档中文版.zip”包含了一个.chm( Compiled HTML Help)文件,这是一种常见的帮助...
在这个例子中,我们首先获取系统Java编译器实例,然后创建一个标准文件管理器,用于处理源文件对象。接着,我们将要编译的.java文件转换为`JavaFileObject`,并调用`compiler.compile()`进行编译。 编译完成后,...
首先,我们需要了解Java Compiler API,这是Java 6引入的一个新特性,允许我们在运行时编译Java源代码。`javax.tools.ToolProvider`类提供了获取Java编译器的静态方法,`javax.tools.JavaCompiler`接口则提供了实际...
Java提供了一个内置的`javax.tools.JavaCompiler`接口,它是Java工具接口(Java Tool API)的一部分,允许我们在程序中调用Javac编译器。要使用这个接口,首先需要引入`tools.jar`库,这个库通常位于Java安装目录的...