系列中的上一篇
当前教程
Javac - 编译器
系列中的下一篇

系列中的上一篇: 介绍核心 JDK 工具

系列中的下一篇: Javap - 反汇编器

Javac - 编译器

您可以使用基础 JDK 工具和命令来创建和构建应用程序。以下部分介绍了可用于创建和构建应用程序的工具和命令。

 

介绍 javac

javac - 读取 Java 类和接口定义,并将它们编译成字节码和类文件。

javac [options] [sourcefiles]

选项 命令行选项。

源文件 要编译(例如 MyClass.java)或处理注释(例如 MyPackage.MyClass)的一个或多个源文件。

 

描述

javac 命令读取用 Java 编程语言编写的类和接口定义,并将它们编译成字节码类文件。javac 命令还可以处理 Java 源文件和类中的注释。

在 JDK 9 中引入了一个启动器环境变量 JDK_JAVAC_OPTIONS,它将内容预先添加到命令行,以传递给 javac。

有两种方法可以将源代码文件名传递给 javac

  • 对于少量源文件,您可以在命令行中列出文件名。

  • 对于少量源文件,您可以在 javac 命令行中使用 @filename 选项来包含一个列出源文件名的文件。

源代码文件名必须具有 .java 后缀,类文件名必须具有 .class 后缀,并且源文件和类文件都必须具有标识类的根名称。例如,名为 MyClass 的类将写入名为 MyClass.java 的源文件中。

class MyClass {
    public static void main(String[] args) {
        System.out.println("this is my class");
    }
}

并编译成名为 MyClass.class 的字节码类文件。

javac MyClass.java
MyClass.java
MyClass.class

内部类定义会生成额外的类文件。这些类文件的名称将组合内部类和外部类名称,例如 MyClass$MyInnerClass.class。

您应该将源文件排列在反映其包树的目录树中。例如

  • Linux 和 macOS: 如果您的所有源文件都在 /workspace 中,则将 com.mysoft.mypack.MyClass 的源代码放在 /workspace/com/mysoft/mypack/MyClass.java 中。
  • Windows: 如果您的所有源文件都在 \workspace 中,则将 com.mysoft.mypack.MyClass 的源代码放在 \workspace\com\mysoft\mypack\MyClass.java 中。

默认情况下,编译器将每个类文件放在与其源文件相同的目录中。您可以使用标准选项中描述的 -d 选项指定单独的目标目录。

 

编程接口

javac 命令支持由 javax.tools 包中的类和接口定义的新 Java 编译器 API。

 

隐式加载的源文件

要编译一组源文件,编译器可能需要隐式加载其他源文件。这些文件目前不受注释处理的约束。默认情况下,编译器在发生注释处理并且编译任何隐式加载的源文件时会发出警告。-implicit 选项提供了一种抑制警告的方法。

 

使用 JDK_JAVAC_OPTIONS 环境变量

JDK_JAVAC_OPTIONS 环境变量的内容(由空格 或空格字符(\n\t\r\f)分隔)将作为参数列表预先添加到传递给 javac 的命令行参数中。

环境变量的编码要求与系统上的 javac 命令行相同。JDK_JAVAC_OPTIONS 环境变量内容的处理方式与命令行中指定的方式相同。

单引号 ' 或双引号 " 可用于括起包含空格字符的参数。打开引号和第一个匹配的关闭引号之间的所有内容都将通过简单地删除一对引号来保留。如果找不到匹配的引号,启动器将中止并显示错误消息。@files 受支持,因为它们是在命令行中指定的。但是,与 @files 中一样,不支持使用通配符。

export JDK_JAVAC_OPTIONS='@"C:\white spaces\argfile"'

export JDK_JAVAC_OPTIONS='"@C:\white spaces\argfile"'

export JDK_JAVAC_OPTIONS='@C:\"white spaces"\argfile'

javac 选项概述

编译器有一组标准选项和跨编译选项,这些选项在当前开发环境中受支持。编译器还具有一组非标准选项,这些选项特定于当前虚拟机和编译器实现,但将来可能会发生更改。非标准选项以 -X 开头。以下部分介绍了不同类型的 javac 选项。

  • 标准选项
  • javac 的跨编译选项
  • 额外选项

标准选项

@filename 从文件读取选项和文件名。为了缩短或简化 javac 命令,您可以指定一个或多个包含 javac 命令参数的文件(除了 -J 选项)。这使您可以在任何操作系统上创建任意长度的 javac 命令。

-Akey[=value] 指定要传递给注释处理器的选项。这些选项不会被 javac 直接解释,而是可供各个处理器使用。键值应为一个或多个由点 . 分隔的标识符。

--add-modules module,module 指定除初始模块之外要解析的根模块,或者如果模块为 ALL-MODULE-PATH,则指定模块路径上的所有模块。

--boot-class-path path-bootclasspath path 覆盖引导类文件的路径。

--class-path path-classpath path-cp path 指定在何处查找用户类文件和注释处理器。此类路径将覆盖 CLASSPATH 环境变量中的用户类路径。

  • 如果未指定 --class-path、-classpath 或 -cp,则用户类路径为当前目录。
  • 如果未指定 -sourcepath 选项,则还会在用户类路径中搜索源文件。
  • 如果未指定 -processorpath 选项,则还会在类路径中搜索注释处理器。

-d directory 设置类文件的目标目录。如果类是包的一部分,则 javac 将类文件放在反映包名称的子目录中,并根据需要创建目录。例如

  • Linux 和 macOS: 如果您指定 -d /home/myclasses 并且类名为 com.mypackage.MyClass,则类文件为 /home/myclasses/com/mypackage/MyClass.class
  • Windows: 如果您指定 -d C:\myclasses 并且类名为 com.mypackage.MyClass,则类文件为 C:\myclasses\com\mypackage\MyClass.class

-deprecation 显示对已弃用成员或类的每次使用或覆盖的描述。如果没有 -deprecation 选项,javac 将显示使用或覆盖已弃用成员或类的源文件的摘要。-deprecation 选项是 -Xlint:deprecation 的简写。

--enable-preview 启用预览语言功能。与 -source 或 --release 结合使用。

-encoding encoding 指定源文件使用的字符编码,例如 EUC-JP 和 UTF-8。如果未指定 -encoding 选项,则使用平台默认编码。

-endorseddirs directories 覆盖认可标准路径的位置。

-extdirs directories 覆盖已安装扩展的位置。directories 变量是一个用冒号分隔的目录列表。将在指定的目录中的每个 JAR 文件中搜索类文件。找到的所有 JAR 文件都将成为类路径的一部分。

-g 生成所有调试信息,包括局部变量。默认情况下,只生成行号和源文件信息。

-g:[lines, vars, source] 只生成由逗号分隔的关键字列表指定的调试信息类型。有效的关键字是

`lines` - Line number debugging information.

`vars` - Local variable debugging information.

`source` - Source file debugging information.

-g:none 不生成调试信息。

-h directory 指定放置生成的本机头文件的位置。

当您指定此选项时,将为每个包含本机方法或具有一个或多个用 java.lang.annotation.Native 注释的常量的类生成一个本机头文件。如果类是包的一部分,则编译器将本机头文件放在反映包名称的子目录中,并根据需要创建目录。

--help, –help 或 -? 打印标准选项的概要。

--help-extra 或 -X 打印额外选项的帮助信息。

-implicit:[none, class] 指定是否为隐式引用的文件生成类文件。

-implicit:class 自动生成类文件。

-implicit:none 抑制类文件生成。

如果未指定此选项,则默认情况下会自动生成类文件。在这种情况下,如果在执行注释处理时也生成了任何类文件,则编译器会发出警告。当显式设置 -implicit 选项时,不会发出警告。请参阅搜索类型。

-Joption 将选项传递给运行时系统,其中 option 是 java 命令中描述的 Java 选项之一。例如,-J-Xms48m 将启动内存设置为 48 MB。

--limit-modules module,module* 限制可观察模块的范围。

--module module-name 或 -m module-name 只编译指定的模块并检查时间戳。

--module-path path 或 -p path 指定在何处查找应用程序模块。

--module-source-path module-source-path 指定在何处查找多个模块的输入源文件。

--module-version version 指定正在编译的模块的版本。

-nowarn 禁用警告消息。此选项与 -Xlint:none 选项的作用相同。

-parameters 为方法参数生成反射元数据。将构造函数和方法的形式参数名称存储在生成的类文件中,以便反射 API 中的 java.lang.reflect.Executable.getParameters 方法可以检索它们。

-proc:[none, only] 控制是否执行注释处理和编译。-proc:none 表示编译在没有注释处理的情况下进行。-proc:only 表示只执行注释处理,不进行任何后续编译。

-processor class1[,class2,class3...] 要运行的注释处理器的名称。这将绕过默认的发现过程。

--processor-module-path path 指定用于查找注释处理器的模块路径。

--processor-path path-processorpath path 指定在何处查找注释处理器。如果未使用此选项,则将在类路径中搜索处理器。

-profile profile 检查指定的配置文件中是否可以使用所用的 API。

--release release 针对特定 VM 版本的公共、受支持和已记录的 API 进行编译。

指定用于放置生成的源文件的目录。如果一个类属于一个包,那么编译器会将源文件放在一个反映包名的子目录中,并根据需要创建目录。例如

  • Linux 和 macOS:如果您指定 -s /home/mysrc 并且类名为 com.mypackage.MyClass,那么源文件将放在 /home/mysrc/com/mypackage/MyClass.java 中。
  • Windows:如果您指定 -s C:\mysrc 并且类名为 com.mypackage.MyClass,那么源文件将放在 C:\mysrc\com\mypackage\MyClass.java 中。

-source release 指定接受的源代码版本。允许以下 release 值

-source 15 编译器接受在 Java SE 15 中引入的功能的代码。

-source 16 默认值。编译器接受在 Java SE 16 中引入的功能的代码。

--source-path path-sourcepath path 指定在何处查找输入源文件。这是用于搜索类或接口定义的源代码路径。与用户类路径一样,源路径条目在 Linux 和 macOS 上用冒号 (:) 分隔,在 Windows 上用分号 (;) 分隔。它们可以是目录、JAR 存档或 ZIP 存档。如果使用包,那么目录或存档中的本地路径名必须反映包名。

--system jdk | none 覆盖系统模块的位置。

-target release 为特定 VM 版本生成类文件。

--upgrade-module—path path 覆盖可升级模块的位置。

-verbose 输出有关编译器正在执行的操作的消息。消息包括有关加载的每个类和编译的每个源文件的信息。

--version-version 打印版本信息。

-Werror 当出现警告时终止编译。

 

javac 的跨编译选项

默认情况下,对于 JDK 9 之前的版本,类是针对与 javac 命令一起提供的平台的引导类进行编译的。但是 javac 也支持交叉编译,其中类是针对不同 Java 平台实现的引导类进行编译的。在交叉编译时,使用 -bootclasspath-extdirs 选项非常重要。

 

额外选项

--add-exports module/package=other-module(,other-module)* 指定一个包,当 other-module 的值为 ALL-UNNAMED 时,该包将被视为从其定义模块导出到其他模块或所有未命名模块。

--add-reads module=other-module(,other-module)* 指定其他模块,这些模块将被视为由给定模块所需。

--default-module-for-created-files module-name 指定注释处理器创建的文件的回退目标模块,如果未指定或推断出任何模块。

-Djava.endorsed.dirs=dirs 覆盖认可标准路径的位置。

-Djava.ext.dirs=dirs 覆盖已安装扩展的位置。

--doclint-format [html4|html5] 指定文档注释的格式。

--patch-module module=file(:file)* 使用 JAR 文件或目录中的类和资源覆盖或增强模块。

-Xbootclasspath:path 覆盖引导类文件的位置。

-Xbootclasspath/a:path 在引导类路径中添加后缀。

-Xbootclasspath/p:path 在引导类路径中添加前缀。

-Xdiags:[compact, verbose] 选择诊断模式。

-Xdoclint 启用对 javadoc 注释中问题的推荐检查。

-Xdoclint:(all|none|[-]group)[/access] 启用或禁用特定组的检查。

group 可以具有以下值之一

    accessibility
    html
    missing
    reference
    syntax

access 指定 -Xdoclint 选项检查的类和成员的最低可见性级别。它可以具有以下值之一(按从最可见到最不可见排序)

    public
    protected
    package
    private

默认访问级别为 private

有关这些检查组的更多信息,请参阅 javadoc 命令的 -Xdoclint 选项。-Xdoclint 选项在 javac 命令中默认情况下处于禁用状态。

例如,以下选项检查具有受保护和更高访问级别(包括受保护和公共)的类和成员(使用所有组的检查)

-Xdoclint:all/protected
-Xdoclint:all,-html/package

-Xdoclint/package:[-]packages(,[-]package)* 启用或禁用特定包中的检查。每个包要么是包的限定名称,要么是包名称前缀后跟一个句点和星号 (.*),它扩展到给定包的所有子包。每个包都可以用连字符 (-) 为前缀,以禁用对指定包或包的检查。

-Xlint 启用所有推荐的警告。在此版本中,建议启用所有可用的警告。

-Xlint:[-]key(,[-]key)* 提供要启用或禁用的警告,用逗号 (,) 分隔。在键前加上连字符 (-) 以禁用指定的警告。

key 的支持值是

        all: Enables all warnings.
        auxiliaryclass: Warns about an auxiliary class that’s hidden in a source file, and is used from other files.
        cast: Warns about the use of unnecessary casts.
        classfile: Warns about the issues related to classfile contents.
        deprecation: Warns about the use of deprecated items.
        dep-ann: Warns about the items marked as deprecated in javadoc but without the @Deprecated annotation.
        divzero: Warns about the division by the constant integer 0.
        empty: Warns about an empty statement after if.
        exports: Warns about the issues regarding module exports.
        fallthrough: Warns about the falling through from one case of a switch statement to the next.
        finally: Warns about finally clauses that do not terminate normally.
        module: Warns about the module system-related issues.
        opens: Warns about the issues related to module opens.
        options: Warns about the issues relating to use of command line options.
        overloads: Warns about the issues related to method overloads.
        overrides: Warns about the issues related to method overrides.
        path: Warns about the invalid path elements on the command line.
        processing: Warns about the issues related to annotation processing.
        rawtypes: Warns about the use of raw types.
        removal: Warns about the use of an API that has been marked for removal.
        requires-automatic: Warns developers about the use of automatic modules in requires clauses.
        requires-transitive-automatic: Warns about automatic modules in requires transitive.
        serial: Warns about the serializable classes that do not provide a serial version ID. Also warns about access to non-public members from a serializable element.
        static: Warns about accessing a static member using an instance.
        try: Warns about the issues relating to the use of try blocks (that is, try-with-resources).
        unchecked: Warns about the unchecked operations.
        varargs: Warns about the potentially unsafe vararg methods.
        none: Disables all warnings.

-Xmaxerrs number 设置要打印的最大错误数。

-Xmaxwarns number 设置要打印的最大警告数。

-Xpkginfo:[always, legacy, nonempty] 指定 javac 命令何时以及如何使用以下选项之一从 package-info.java 文件生成 package-info.class 文件

    always - Generates a package-info.class file for every package-info.java file. This option may be useful if you use a build system such as Ant, which checks that each .java file has a corresponding .class file.
    legacy - Generates a package-info.class file only if package-info.java contains annotations. This option doesn't generate a package-info.class file if package-info.java contains only comments.
    nonempty - Generates a package-info.class file only if package-info.java contains annotations with RetentionPolicy.CLASS or RetentionPolicy.RUNTIME.

-Xplugin:name args 指定要运行的插件的名称和可选参数。

-Xprefer:[source, newer] 指定当为使用以下选项之一的隐式编译类找到源文件和类文件时,要读取哪个文件

`-Xprefer:newer` - Reads the newer of the source or class files for a type (default).

`-Xprefer:source` - Reads the source file. Use `-Xprefer:source` when you want to be sure that any annotation processors can access annotations declared with a retention policy of SOURCE.

-Xprint 打印指定类型的文本表示形式,用于调试目的。这不会执行注释处理或编译。输出的格式可能会更改。

-XprintProcessorInfo 打印有关请求处理器处理哪些注释的信息。

-XprintRounds 打印有关初始和后续注释处理轮次的信息。

-Xstdout filename 将编译器消息发送到指定的文件。默认情况下,编译器消息会发送到 System.err。

 

命令行参数文件

参数文件可以包含 javac 选项和源文件名,以任何组合方式。文件中的参数可以用空格或换行符分隔。如果文件名包含嵌入的空格,则将整个文件名放在双引号中。

参数文件中的文件名相对于当前目录,而不是相对于参数文件的位置。这些列表中不允许使用通配符 *(例如,用于指定 *.java)。不支持使用 at 符号 @ 递归解释文件。-J 选项不受支持,因为它们传递给启动器,启动器不支持参数文件。

执行 javac 命令时,使用 at 符号 @ 作为前导字符传递每个参数文件的路径和名称。当 javac 命令遇到以 at 符号 @ 开头的参数时,它会将该文件的内容扩展到参数列表中。

 

示例

使用 javac @filename 的示例

单个参数文件 您可以使用名为 argfile 的单个参数文件来保存所有 javac 参数

javac @argfile

此参数文件可以包含以下两个参数文件示例中显示的两个文件的全部内容。

两个参数文件 您可以创建两个参数文件:一个用于 javac 选项,另一个用于源文件名。请注意,以下列表没有行延续字符。创建一个名为 options 的文件,其中包含以下内容

Linux 和 macOS

-d classes
-g
-sourcepath /java/pubs/ws/1.3/src/share/classes

Windows

-d classes
-g
-sourcepath C:\java\pubs\ws\1.3\src\share\classes

创建一个名为 classes 的文件,其中包含以下内容

MyClass1.java
MyClass2.java
MyClass3.java

然后,运行 javac 命令,如下所示

javac @options @classes

带有路径的参数文件 参数文件可以有路径,但文件中的任何文件名都相对于当前工作目录(而不是 path1 或 path2)

javac @path1/options @path2/classes

使用 -Xlint 键的示例

cast 警告不必要的和多余的强制转换,例如

String s = (String) "Hello!"

classfile 警告与类文件内容相关的问题。

deprecation 警告使用已弃用的项目。例如

java.util.Date myDate = new java.util.Date();
int currentDay = myDate.getDay();

方法 java.util.Date.getDay 自 JDK 1.1 以来已弃用。

dep-ann 警告使用 @deprecated Javadoc 注释但没有 @Deprecated 注释的项目,例如

/**
  * @deprecated As of Java SE 7, replaced by {@link #newMethod()}
  */
public static void deprecatedMethod() { }
public static void newMethod() { }

divzero 警告除以常量整数 0,例如

int divideByZero = 42 / 0;

empty 警告 if 语句后的空语句,例如

class E {
    void m() {
         if (true) ;
    }
}

fallthrough 检查 switch 块中的贯穿情况,并为找到的任何贯穿情况提供警告消息。贯穿情况是指 switch 块中的情况,除了块中的最后一种情况外,其代码不包含 break 语句,允许代码执行从该情况贯穿到下一种情况。例如,此 switch 块中 case 1 标签后的代码没有以 break 语句结尾

switch (x) {
case 1:
  System.out.println("1");
  // No break statement here.
case 2:
  System.out.println("2");
}

如果在编译此代码时使用了 -Xlint:fallthrough 选项,那么编译器会发出有关可能贯穿到 case 的警告,并给出 case 的行号。

finally 警告无法正常完成的 finally 子句,例如

public static int m() {
  try {
     throw new NullPointerException();
  }  catch (NullPointerException(); {
     System.err.println("Caught NullPointerException.");
     return 1;
   } finally {
     return 0;
   }
  }

编译器会为此示例中的 finally 块生成警告。调用 int 方法时,它会返回 0 的值。finally 块在 try 块退出时执行。在此示例中,当控制权转移到 catch 块时,int 方法退出。但是,finally 块必须执行,因此它会执行,即使控制权已转移到方法之外。

options 警告与命令行选项的使用相关的问题。请参阅 javac 的交叉编译选项。

overrides 警告与方法覆盖相关的问题。例如,考虑以下两个类

public class ClassWithVarargsMethod {
  void varargsMethod(String... s) { }
}

public class ClassWithOverridingMethod extends ClassWithVarargsMethod {
   @Override
   void varargsMethod(String[] s) { }
}

编译器会生成类似于以下内容的警告

warning: [override] varargsMethod(String[]) in ClassWithOverridingMethod 
overrides varargsMethod(String...) in ClassWithVarargsMethod; overriding
method is missing '...'

当编译器遇到可变参数方法时,它会将可变参数形式参数转换为数组。在方法 ClassWithVarargsMethod.varargsMethod 中,编译器将可变参数形式参数 String... s 转换为形式参数 String[] s,这是一个与方法 ClassWithOverridingMethod.varargsMethod 的形式参数匹配的数组。因此,此示例可以编译。

path 警告命令行中无效的路径元素和不存在的路径目录(关于类路径、源路径和其他路径)。此类警告无法使用 @SuppressWarnings 注释抑制。例如

  • Linux 和 macOS: javac -Xlint:path -classpath /nonexistentpath Example.java

  • Windows: javac -Xlint:path -classpath C:\nonexistentpath Example.java

processing 警告与注释处理相关的问题。当您有一个带有注释的类,并且您使用的注释处理器无法处理该类型的异常时,编译器会生成此警告。例如,以下是一个简单的注释处理器

import java.util.*;
import javax.annotation.processing.*;
import javax.lang.model.*;
import javaz.lang.model.element.*;

@SupportedAnnotationTypes("NotAnno")
public class AnnoProc extends AbstractProcessor {
  public boolean process(Set<? extends TypeElement> elems, RoundEnvironment renv){
     return true;
  }

  public SourceVersion getSupportedSourceVersion() {
     return SourceVersion.latest();
   }
}
@interface Anno { }

@Anno
class AnnosWithoutProcessors { }

以下命令编译注释处理器 AnnoProc,然后针对源文件 AnnosWithoutProcessors.java 运行此注释处理器

javac AnnoProc.java
javac -cp . -Xlint:processing -processor AnnoProc -proc:only AnnosWithoutProcessors.java

当编译器针对源文件 AnnosWithoutProcessors.java 运行注释处理器时,它会生成以下警告

warning: [processing] No processor claimed any of these annotations: Anno

要解决此问题,您可以将类 AnnosWithoutProcessors 中定义和使用的注释从 Anno 重命名为 NotAnno

rawtypes 警告关于对原始类型的未经检查的操作。以下语句会生成 rawtypes 警告

void countElements(List l) { ... }

以下示例不会生成 rawtypes 警告

void countElements(List<?> l) { ... }

List 是一个原始类型。但是,List<?> 是一个无界通配符参数化类型。因为 List 是一个参数化接口,所以始终指定其类型参数。在此示例中,List 形式参数使用无界通配符 ? 作为其形式类型参数,这意味着 countElements 方法可以接受 List 接口的任何实例化。

serial 警告关于可序列化类中缺少 serialVersionUID 定义。例如

public class PersistentTime implements Serializable
{
  private Date time;
 
   public PersistentTime() {
     time = Calendar.getInstance().getTime();
   }
 
   public Date getTime() {
     return time;
   }
}

编译器会生成以下警告

warning: [serial] serializable class PersistentTime has no definition of
serialVersionUID

如果可序列化类没有显式声明名为 serialVersionUID 的字段,则序列化运行时环境会根据类的各个方面为该类计算一个默认的 serialVersionUID 值,如 Java 对象序列化规范中所述。但是,强烈建议所有可序列化类显式声明 serialVersionUID 值,因为计算 serialVersionUID 值的默认过程对类细节非常敏感,这些细节可能会因编译器实现而异。因此,这可能会在反序列化期间导致意外的 InvalidClassExceptions。为了保证在不同的 Java 编译器实现中一致的 serialVersionUID 值,可序列化类必须声明一个显式的 serialVersionUID 值。

static 警告与静态变量的使用相关的问题,例如

class XLintStatic {
    static void m1() { }
    void m2() { this.m1(); }
}

编译器会生成以下警告

warning: [static] static method should be qualified by type name,
XLintStatic, instead of by an expression

要解决此问题,您可以按如下方式调用静态方法 m1

XLintStatic.m1();

或者,您可以从方法 m1 的声明中删除 static 关键字。

try 警告与 try 块的使用相关的问题,包括 try-with-resources 语句。例如,以下语句会生成警告,因为在 try 块中声明的资源 ac 未使用

try ( AutoCloseable ac = getResource() ) {    // do nothing}

unchecked 为 Java 语言规范强制执行的未经检查的转换警告提供更多详细信息,例如

List l = new ArrayList<Number>();
List<String> ls = l;       // unchecked warning

在类型擦除期间,类型 ArrayList<Number>List<String> 分别变为 ArrayListList

ls 命令具有参数化类型 List<String>。当 l 引用的 List 被分配给 ls 时,编译器会生成一个未经检查的警告。在编译时,编译器和 JVM 无法确定 l 是否引用 List<String> 类型。在这种情况下,l 不会引用 List<String> 类型。因此,会发生堆污染。

List 对象 l(其静态类型为 List<Number>)被分配给另一个 List 对象 ls(具有不同的静态类型 List<String>)时,就会发生堆污染情况。但是,编译器仍然允许此分配。它必须允许此分配以保持与不支持泛型的 Java SE 版本的向后兼容性。由于类型擦除,List<Number>List<String> 都变为 List。因此,编译器允许将具有原始类型 List 的对象 l 分配给对象 ls。

varargs 警告关于不安全使用可变参数 (varargs) 方法,特别是那些包含不可再现参数的方法,例如

public class ArrayBuilder {
  public static <T> void addToList (List<T> listArg, T... elements) {
    for (T x : elements) {
      listArg.add(x);
    }
  }
}

不可再现类型是一种在运行时无法完全获得类型信息的类型。编译器为方法 ArrayBuilder.addToList 的定义生成以下警告

warning: [varargs] Possible heap pollution from parameterized vararg type T

当编译器遇到可变参数方法时,它会将可变参数形式参数转换为数组。但是,Java 编程语言不允许创建参数化类型的数组。在方法 ArrayBuilder.addToList 中,编译器将可变参数形式参数 T... elements 转换为形式参数 T[] elements,这是一个数组。但是,由于类型擦除,编译器将可变参数形式参数转换为 Object[] elements。因此,存在堆污染的可能性。

通过提供命令行参数进行编译的示例

要像提供命令行参数一样进行编译,请使用以下语法

JavaCompiler javac = ToolProvider.getSystemJavaCompiler();

此示例将诊断信息写入标准输出流,并返回 javac 命令从命令行调用时将给出的退出代码。您可以使用 javax.tools.JavaCompiler 接口中的其他方法来处理诊断信息,控制从哪里读取和写入文件等等。

编译多个源文件的示例

此示例编译 greetings 包中的 Aloha.javaGutenTag.javaHello.javaHi.java 源文件。

Linux 和 macOS

% javac greetings/*.java
% ls greetings
Aloha.class         GutenTag.class      Hello.class         Hi.class
Aloha.java          GutenTag.java       Hello.java          Hi.java

Windows

C:\>javac greetings\*.java
C:\>dir greetings
Aloha.class         GutenTag.class      Hello.class         Hi.class
Aloha.java          GutenTag.java       Hello.java          Hi.java

指定用户类路径的示例

更改上一个示例中的一个源文件后,重新编译它

Linux 和 macOS

pwd
/examples
javac greetings/Hi.java

Windows

C:\>cd
\examples
C:\>javac greetings\Hi.java

因为 greetings.Hi 引用了 greetings 包中的其他类,所以编译器需要找到这些其他类。上一个示例之所以有效,是因为默认的用户类路径是包含包目录的目录。如果您想在不关心当前目录的情况下重新编译此文件,则通过设置 CLASSPATH 将示例目录添加到用户类路径。此示例使用 -classpath 选项。

Linux 和 macOS

javac -classpath /examples /examples/greetings/Hi.java

Windows

C:\>javac -classpath \examples \examples\greetings\Hi.java
If you change greetings.Hi to use a banner utility, then that utility also needs to be accessible through the user class path.

Linux 和 macOS

javac -classpath /examples:/lib/Banners.jar /examples/greetings/Hi.java

Windows

C:\>javac -classpath \examples;\lib\Banners.jar \examples\greetings\Hi.java

要执行 greetings 包中的类,程序需要访问 greetings 包,以及 greetings 类使用的类。

Linux 和 macOS

java -classpath /examples:/lib/Banners.jar greetings.Hi

Windows

C:\>java -classpath \examples;\lib\Banners.jar greetings.Hi

 

注释处理

javac 命令直接支持注释处理,取代了对单独的注释处理命令 apt 的需求。注释处理器的 API 定义在 javax.annotation.processing 和 javax.lang.model 包及子包中。

注释处理的工作原理

除非使用 -proc:none 选项禁用注释处理,否则编译器会搜索任何可用的注释处理器。搜索路径可以使用 -processorpath 选项指定。如果未指定路径,则使用用户类路径。处理器通过名为 META-INF/services/javax.annotation.processing 的服务提供者配置文件来定位。搜索路径上的处理器。此类文件应包含要使用的任何注释处理器的名称,每行列出一个。或者,可以使用 -processor 选项显式指定处理器。在扫描命令行上的源文件和类以确定存在哪些注释后,编译器会查询处理器以确定它们处理哪些注释。当找到匹配项时,就会调用处理器。处理器可以声明它处理的注释,在这种情况下,不会再尝试为这些注释查找任何处理器。在声明所有注释后,编译器不会再搜索其他处理器。

如果任何处理器生成新的源文件,则会进行另一轮注释处理:扫描任何新生成的源文件,并像以前一样处理注释。在先前轮次中调用的任何处理器也会在所有后续轮次中被调用。这将持续进行,直到不再生成新的源文件。在没有生成新的源文件的一轮之后,会最后一次调用注释处理器,以便它们有机会完成任何剩余的工作。最后,除非使用-proc:only选项,否则编译器将编译原始文件和所有生成的源文件。

搜索类型

为了编译源文件,编译器通常需要有关类型的的信息,但类型定义不在命令行上指定的源文件中。编译器需要每个在源文件中使用、扩展或实现的类或接口的类型信息。这包括源文件中未明确提及的类和接口,但它们通过继承提供信息。

例如,当您创建 java.awt.Window 的子类时,您也在使用 Window 的祖先类:java.awt.Container、java.awt.Componentjava.lang.Object。当编译器需要类型信息时,它会搜索定义该类型的源文件或类文件。编译器首先在引导类和扩展类中搜索类文件,然后在用户类路径(默认情况下是当前目录)中搜索。用户类路径通过设置 CLASSPATH 环境变量或使用-classpath选项来定义。

如果设置了-sourcepath选项,则编译器会在指定的路径中搜索源文件。否则,编译器会在用户类路径中搜索类文件和源文件。您可以使用-bootclasspath-extdirs选项指定不同的引导类或扩展类。

成功的类型搜索可能会生成类文件、源文件或两者。如果两者都找到,则可以使用-Xprefer选项指示编译器使用哪个。如果指定了 newer,则编译器将使用这两个文件中的较新者。如果指定了 source,则编译器将使用源文件。默认值为 newer。

如果类型搜索找到所需类型的源文件,无论是单独找到还是由于-Xprefer选项的设置而找到,则编译器会读取源文件以获取所需的信息。默认情况下,编译器还会编译源文件。您可以使用 -implicit 选项指定行为。如果未指定,则不会为源文件生成任何类文件。如果指定了 class,则会为源文件生成类文件。

编译器可能直到注释处理完成后才会发现对某些类型信息的需要。当类型信息在源文件中找到并且没有指定-implicit选项时,编译器会发出警告,指出该文件正在编译,但没有经过注释处理。要禁用警告,请在命令行上指定文件(以便它会经过注释处理)或使用-implicit选项指定是否应为这些源文件生成类文件。


上次更新: 2021 年 9 月 14 日


系列中的上一篇
当前教程
Javac - 编译器
系列中的下一篇

系列中的上一篇: 介绍核心 JDK 工具

系列中的下一篇: Javap - 反汇编器