java编译器API<一>

从java6.0开始,新增了“java 编译器API”的特性,在此之前,我们都是通过调用javac.exe来进行编译,从java6.0开始,编译更加灵活,让码农们有更广阔的思考的空间。

1.编译器API

开发者运行java编译器所需要的所有API,都可以在javax.tools包下面找到,这个包下面不仅有引用一个java编译器需要的类和方法,同时,它还提供了任何类型工具的公共接口,这些工具一般是命令行程序,比如javac.exe,javadoc.exe或者javah.exe.
扩展阅读(陆续被翻译):

  • java6.0新特性(1)
  • java6.0新特性(2)
  • java6.0中JDBC API的新特性
  • java8.0新特性

如果你有精力去阅读javax.tools包下面的类和接口来剖析编译器API,那固然很好。大多数时候,我们是没有这么多的精力去做这个事情的。那么,能通过简单的例子来了解这些新的东西,也是一个不错的选择.

1.1通过一个java文件编译另一个java文件

接下来用一个小例子来描述一下java 编译器API是如何工作的,例子中共有两个java文件A.java和SimpleCompileTest.java文件,SimpleCompileTest.java来编译A.java

package com.java.compiler; 
public class A { 
    public static void main(String args[]){ 
       System.out.println("r u ok ?"); 
    } 
}
package com.java.compiler; 
import javax.tools.JavaCompiler; 
import javax.tools.ToolProvider; 
public class SimpleCompileTest { 
    public static void main(String args[]){ 
        //java文件的路径全称 
        String fileToCompile = "com/java/compiler/A.java"; 
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); 
        int compilationResult = compiler.run(null, null, null, fileToCompile); 
        if(0==compilationResult){ 
            System.out.println("Compilation is successful"); 
        }else{ 
            System.out.println("Compilation Failed"); 
        } 
    } 
}

那看一下上面的这些代码,获取编译器示例的入口依赖于ToolProvider类,这个类提供了获取一个Tool实例的方法(备注:一个工具可能是javac\javadoc\rmic\javah等等中的任何一个),在java6.0中,能获取的工具就是java编译器,我们希望在以后的的版本中,可以增加获取更多工具的特性。 ToolProvider类中的getSystemJavaCompiler方法,返回一个实现了JavaCompiler接口的类的一个实例(JavaCompiler是一个继承了Tool接口的接口,不是一个类)。在tools.jar中可以找到JavaCompiler的具体实现(有兴趣的童鞋可以挖一下代码) 获取JavaCompiler实例之后,调用run方法进行编译,对于这个 run(InputStream inputStream, OutputStream outputStream, OutputStream errorStream, String … arguments)方法,相应的代码注释是:

  • in “standard” input; use System.in if null
  • out “standard” output; use System.out if null
  • err “standard” error; use System.err if null
  • arguments arguments to pass to the tool
  • Returns:0 for success; nonzero otherwise

从上面的参数注释可以看出,前三个参数都可以传null的,会有默认值与之对应,最后一个参数,就是和编译java文件相关的参数(就像上面的例子中,要编译的文件fileToCompile在最后一个参数中传入)。本方法如果返回0,表示编译成功,否则,编译失败。
这时,我们运行SimpleCompileTest,你会看到“Compilation is successful”的信息打印出来。说明,A.java编译成功了。
我们现在再看一下,源代码有错误的情况下,进行编译的时候,会报什么信息。现在我们把A.java修改成下面的样子(去掉System.out.println(“r u ok ?”)后面的分号):

package com.java.compiler; 
public class A { 
public static void main(String args[]){ 
System.out.println("r u ok ?") 
} 
}

现在,进行编译,你会看到类似下面的输出信息:

com\java\compiler\A.java:6: 需要 ‘;’ 
     System.out.println(“r u ok ?”) 
     ^ 1 

错误 Compilation Failed

这些错误,是我们在javac的时候,经常碰到的问题。在这里,这个错误信息,打印在控制台上面。这是因为,在run方法的第三个参数中,我们传入的是null值,系统就默认为System.err,然后就打印在控制台上面了。
如果你想把错误的信息,打印在别处,可以自己制定特定的错误流,比如:

       FileOutputStream errorStream = new FileOutputStream(“Errors.txt”); 

       int compilationResult = compiler.run(null, null, errorStream, fileToCompile);

在需要输出错误信息的时候,Errors.txt文件就会被创建在当前目录下面,然后,错误的信息输入到这个文件中。

1.2编译多个文件

    下面的代码,能否完成同时编译两个文件的任务呢?(假设两个被编译的文件分别为One.java和Two.java)

        String filesToCompile = new String(“One.java Two.java”);

    令人失望的是,运行上面的代码的时候,你将在控制台中看到”Compilation Failed”的信息。JavaCompiler需要特定的选项和参数,这些选项和参数不能使用空格隔开,要使用特定的字符串。

    所以,下面的代码不能工作:

    compiler.run(null,null,null,”One.java Two.java”);

    但是,下面的代码就可以很好的工作:

    compiler.run(null,null,null,”One.java”,”Two.java”);

    下面是编译多个java文件的示例代码:

MyClass.java:
packagetest; 
publicclassMyClass {
} 
MyAnotherClass.java:
packagetest; 
publicclassMyAnotherClass {
} 
MultipleFilesCompileTest.java:
packagetest;
importjavax.tools.*; 
publicclassMultipleFilesCompileTest {
    publicstaticvoidmain(String[] args)throwsException{
        String file1ToCompile ="test"+ java.io.File.separator +"MyClass.java";
        String file2ToCompile ="test"+ java.io.File.separator +"MyAnotherClass.java";
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        intcompilationResult = compiler.run(null,null,null, file1ToCompile, file2ToCompile);
        if(compilationResult ==0){
            System.out.println("Compilation is successful");
        }else{
            System.out.println("Compilation Failed");
        }
    }
}

上面的代码,编译正常,你会在控制台看到”Compilation is successful”的信息。

记住,run方法的最后一个参数是可变参数,它可以接收任意个参数。【从java5.0开始,有了可变参数,调用者可以传入任意个参数,为了证明这个,我们可以看一下下面的一个小例子】。

public int addNumbers(int ...numbers){
    int total = 0;
    for(int temp:numbers){
        total = total + temp;
    }
    return total;
}

可变参数,在参数面前加上…即可,调用者可以使用不同的方法来调用上面的方法,如:

    addNumbers(10,10,30,40);//这个可以正常工作

    addNumbers(10,10);//这个也可以正常工作

可变参数,被当作数组对待的,所以,下面的代码也可以正常工作:

    addNumbers(new int[]{10,34,54});

理想的传可选参数和可选参数的值的形式:

    compiler.run(null,null,null, ”-classpath”, ”PathToClasses”, ”-sourcepath”, ”PathToSources”, ”One.java”, ”Two.java”);

正如我们看到的一样,及时是可选参数和可选参数值,也是以单独的值传进去的。

大家都知道,javac的时候,加上“verbose”选项,可以看到编译过程中的信息(比如解析输入的文件、验证、扫描源文件和class文件的路径、加载需要的class文件、最后在目标目录下面生成class文件)。我们可以通过下面的代码完成这样的功能。

SimpleCompileTestWithVerboseOption.java
package test;
import java.io.FileOutputStream;
import javax.tools.*;
public class SimpleCompileTestWithVerboseOption {
    public static void main(String[] args) throws Exception{
        String fileToCompile = "test" + java.io.File.separator + "MyClass.java";
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        FileOutputStream errorStream = new FileOutputStream("Errors.txt");
        int compilationResult = compiler.run(
                null, null, errorStream, "-verbose", fileToCompile);
        if(compilationResult == 0){
            System.out.println("Compilation is successful");
        }else{
            System.out.println("Compilation Failed");
        }
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>