The javac Command

名称

javac - 读取Java声明并将其编译为类文件

概要

javac [选项] [源文件或类名]

选项
命令行选项。
源文件或类名
要编译的源文件(例如,Shape.java)或要处理注解的先前编译类的名称(例如,geometry.MyShape)。

描述

javac命令读取用Java编程语言编写的包含模块、包和类型声明的源文件,并将它们编译为在Java虚拟机上运行的类文件

javac命令还可以处理Java源文件和类中的注解

源文件必须具有.java文件扩展名。类文件具有.class文件扩展名。源文件和类文件通常具有标识内容的文件名。例如,名为Shape的类将在名为Shape.java的源文件中声明,并编译为名为Shape.class的类文件。

有两种指定源文件给javac的方法:

在命令行或参数文件中指定的源文件的顺序并不重要。javac将这些文件一起编译为一个组,并将自动解析各个源文件中声明之间的任何依赖关系。

javac期望源文件在文件系统上的一个或多个目录层次结构中排列,详见源代码的排列

要编译源文件,javac需要找到源文件中的代码使用、扩展或实现的每个类或接口的声明。这使得javac可以检查代码是否有权访问这些类和接口。您可以使用命令行选项告诉javac在哪里搜索这些类和接口的源文件,而不是显式指定这些类和接口的源文件。如果您之前已经编译了这些源文件,您可以使用选项告诉javac在哪里搜索相应的类文件。所有以"path"结尾的选项在标准选项中有描述,并在配置编译搜索模块、包和类型声明中进一步描述。

默认情况下,javac将每个源文件编译为与源文件相同目录中的类文件。但是,建议使用-d选项指定一个单独的目标目录。

命令行选项环境变量还控制javac执行各种任务的方式:

javac支持为平台早期版本编译,也可以通过多种API之一从Java代码中调用。

选项

javac提供标准选项和非标准或用于高级用途的额外选项

某些选项接受一个或多个参数。如果参数包含空格或其他空白字符,则根据调用javac的环境的约定对值进行引用。如果选项以单破折号(-)开头,则参数应直接跟在选项名称后面,或者应使用冒号(:)或空格进行分隔,具体取决于选项。如果选项以双破折号(--)开头,则参数可以通过空格或等号(=)字符进行分隔,而不需要额外的空格。例如,

-Aname="J. Duke"
-proc:only
-d myDirectory
--module-version 3
--module-version=3

在以下选项列表中,path的参数表示搜索路径,由平台路径分隔符字符(在Windows上为分号;,在其他系统上为冒号:)分隔的文件系统位置列表组成。根据选项的不同,文件系统位置可以是目录、JAR文件或JMOD文件。

标准选项

@文件名
从文件中读取选项和文件名。为了缩短或简化javac命令,您可以指定一个或多个包含javac命令参数(除了-J选项)的文件。这样可以在任何操作系统上创建任意长度的javac命令。请参阅命令行参数文件
-A[=]
指定要传递给注解处理器的选项。这些选项不会被javac直接解释,但可以供各个处理器使用。值应该是一个或多个由点(.)分隔的标识符。
--add-modules 模块,模块
指定要解析的根模块,以及初始模块之外的模块,或者如果模块ALL-MODULE-PATH,则是模块路径上的所有模块。
--boot-class-path 路径-bootclasspath 路径

覆盖引导类文件的位置。

注意:只能在编译JDK 9之前的版本时使用。如适用,请参阅--release-source-target中的描述。对于JDK 9或更高版本,请参阅--system

--class-path 路径-classpath 路径,或 -cp 路径

指定查找用户类文件和注解处理器的位置。此类路径会覆盖CLASSPATH环境变量中的用户类路径。

  • 如果未指定--class-path-classpath-cp,则用户类路径是CLASSPATH环境变量的值(如果设置了),否则是当前目录。

  • 如果不是为模块编译代码,且未指定--source-path或-sourcepath`选项,则还会在用户类路径中搜索源文件。

  • 如果未指定-processorpath选项,则还会在类路径中搜索注解处理器。

-d 目录

设置目标目录(或类输出目录)以存放类文件。如果类属于包的一部分,则javac会将类文件放在反映模块名称(如果适用)和包名称的子目录中。如果这些目录不存在,将会创建它们。

如果未指定-d选项,则javac会将每个类文件放在生成它的源文件所在的相同目录中。

除非为多个模块编译代码,否则类输出目录的内容将以包层次结构组织。当为多个模块编译代码时,输出目录的内容将以模块层次结构组织,每个模块的内容都在单独的子目录中,每个子目录都以包层次结构组织。

注意:当为一个或多个模块编译代码时,将自动检查类输出目录以搜索先前编译的类。当不为模块编译代码时,为了向后兼容,目录不会自动检查以前编译的类,因此建议将类输出目录指定为用户类路径上的一个位置,使用--class-path选项或其替代形式之一。

-deprecation
显示每个使用或覆盖已弃用成员或类的描述。如果没有-deprecation选项,javac会显示使用或覆盖已弃用成员或类的源文件的摘要。-deprecation选项是-Xlint:deprecation的简写。
--enable-preview
启用预览语言特性。与-source--release一起使用。
-encoding 编码
指定源文件使用的字符编码,例如EUC-JP和UTF-8。如果未指定-encoding选项,则使用平台默认转换器。
-endorseddirs 目录

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

注意:只能在编译JDK 9之前的版本时使用。如适用,请参阅--release-source-target中的描述。

-extdirs 目录

覆盖已安装扩展的位置。目录是由平台路径分隔符(在Windows上为;,否则为:)分隔的目录列表。在指定的目录中搜索每个JAR文件的类文件。找到的所有JAR文件都将成为类路径的一部分。

如果您正在为支持扩展机制的平台版本编译,则此选项指定包含扩展类的目录。请参阅[为平台的其他版本编译]。

注意:只能在编译JDK 9之前的版本时使用。如适用,请参阅--release-source-target中的描述。

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

仅生成由逗号分隔的关键字列表指定的调试信息类型。有效的关键字包括:

lines
行号调试信息。
vars
局部变量调试信息。
source
源文件调试信息。
-g:none
不生成调试信息。
-h 目录

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

当您指定此选项时,为包含本地方法或具有一个或多个常量被注解为java.lang.annotation.Native的类生成本机头文件。如果类属于包的一部分,则编译器会将本机头文件放在反映模块名称(如果适用)和包名称的子目录中。如果这些目录不存在,将会创建它们。

--help-help-?
打印标准选项的概要。
--help-extra-X
打印一组额外选项的概要。
--help-lint
打印-Xlint选项支持的键。
-implicit:[none, class]

指定是否为隐式引用的文件生成类文件:

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

  • -implicit:none --- 禁止生成类文件。

如果未指定此选项,则默认情况下会自动生成类文件。在这种情况下,如果同时进行注解处理生成了任何类文件,则编译器会发出警告。当-implicit选项被明确设置时,不会发出警告。请参阅搜索模块、包和类型声明

-J选项

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

注意:CLASSPATH环境变量、-classpath选项、-bootclasspath选项和-extdirs选项不指定用于运行javac的类。尝试使用这些选项和变量自定义编译器实现是有风险的,通常不会达到您想要的效果。如果必须自定义编译器实现,则使用-J选项将选项传递给底层Java启动器。

--limit-modules 模块,模块*
限制可观察模块的范围。
--module 模块名,模块名*)或-m 模块名,模块名*)
编译那些比输出目录中对应文件更新的命名模块中的源文件。
--module-path 路径-p 路径
指定应用程序模块的位置。
--module-source-path 模块源路径
指定在编译多个模块中的代码时要查找源文件的位置。请参阅[编译模式]和模块源路径选项
--module-version 版本
指定正在编译的模块的版本。
-nowarn
禁用警告消息。此选项的操作与-Xlint:none选项相同。
-parameters
为方法参数的反射生成元数据。将构造函数和方法的形式参数名称存储在生成的类文件中,以便Reflection API中的方法java.lang.reflect.Executable.getParameters可以检索它们。
-proc:[none, only, full]
控制是否进行注解处理和编译。 -proc:none 表示编译将在没有注解处理的情况下进行。 -proc:only 表示仅进行注解处理,没有后续编译。 如果未使用此选项,或指定了 -proc:full,则会进行注解处理和编译。
-processor class1[,class2,class3...]
要运行的注解处理器的名称。 这将绕过默认的发现过程。
--processor-module-path path
指定用于查找注解处理器的模块路径。
--processor-path path-processorpath path
指定注解处理器的位置。 如果未使用此选项,则将在类路径中搜索处理器。
-profile profile

检查所使用的 API 是否在指定的配置文件中可用。 此选项已弃用,可能会在将来的版本中删除。

注意: 只能在编译 JDK 9 之前的版本时使用。 如适用,请参阅 --release-source-target 中的描述以获取详细信息。

--release release

根据指定的 Java SE 发行版的规则编译源代码,生成针对该发行版的类文件。 源代码针对指定发行版的 Java SE 和 JDK API 进行编译。

release 的支持值是当前的 Java SE 发行版和有限数量的先前发行版,详细信息请参阅命令行帮助。

对于当前发行版,Java SE API 包括由该发行版中的 Java SE 模块导出的 java.*javax.*org.* 包;JDK API 包括由该发行版中的 JDK 模块导出的 com.*jdk.* 包,以及由标准但非 Java SE 模块导出的 javax.* 包。

对于先前的发行版,Java SE API 和 JDK API 如该发行版中定义的那样。

注意: 使用 --release 时,不能同时使用 --source/-source--target/-target 选项。

注意: 使用 --release 指定支持 Java 平台模块系统的发行版时,不能使用 --add-exports 选项来扩展指定发行版中由 Java SE、JDK 和标准模块导出的包集。

-s directory

指定用于放置生成的源文件的目录。 如果类是包的一部分,则编译器将源文件放在反映模块名称(如果适用)和包名称的子目录中。 如果这些目录或必要的子目录不存在,将会创建它们。

除非编译多个模块的代码,否则源输出目录的内容将以包层次结构组织。 当编译多个模块的代码时,源输出目录的内容将以模块层次结构组织,每个模块的内容都在单独的子目录中,每个子目录都以包层次结构组织。

--source release-source release

根据指定的 Java SE 发行版的规则编译源代码。 release 的支持值是当前的 Java SE 发行版和有限数量的先前发行版,详细信息请参阅命令行帮助。

如果未指定此选项,则默认情况下将根据当前 Java SE 发行版的规则编译源代码。

--source-path path-sourcepath path

指定要查找源文件的位置。 除非一起编译多个模块,否则这是用于搜索类或接口定义的源代码路径。

注意: 通过类路径找到的类在找到其源文件时可能会重新编译。 请参阅 搜索模块、包和类型声明

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

生成适用于指定 Java SE 发行版的 class 文件。 release 的支持值是当前的 Java SE 发行版和有限数量的先前发行版,详细信息请参阅命令行帮助。

注意: 目标发行版必须等于或高于源发行版。 (请参阅 --source。)

--upgrade-module-path path
覆盖可升级模块的位置。
-verbose
输出有关编译器正在执行的操作的消息。 消息包括有关每个加载的类和每个编译的源文件的信息。
--version-version
打印版本信息。
-Werror
在出现警告时终止编译。

额外选项

--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

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

注意:仅在编译 JDK 9 之前的版本时才能使用。如适用,请参阅 --release-source-target 中的描述以获取详细信息。

-Djava.ext.dirs=dirs

覆盖已安装扩展的位置。

注意:仅在编译 JDK 9 之前的版本时才能使用。如适用,请参阅 --release-source-target 中的描述以获取详细信息。

--patch-module module=path
覆盖或增补 JAR 文件或目录中的模块的类和资源。
-Xbootclasspath:path

覆盖引导类文件的位置。

注意:仅在编译 JDK 9 之前的版本时才能使用。如适用,请参阅 --release-source-target 中的描述以获取详细信息。

-Xbootclasspath/a:path

向引导类路径添加后缀。

注意:仅在编译 JDK 9 之前的版本时才能使用。如适用,请参阅 --release-source-target 中的描述以获取详细信息。

-Xbootclasspath/p:path

向引导类路径添加前缀。

注意:仅在编译 JDK 9 之前的版本时才能使用。如适用,请参阅 --release-source-target 中的描述以获取详细信息。

-Xdiags:[compact, verbose]
选择诊断模式。
-Xdoclint
启用对文档注释中问题的推荐检查。
-Xdoclint:(all|none|[-]group)[/access]

启用或禁用文档注释中特定组的检查。

group 可以具有以下值之一:accessibilityhtmlmissingreferencesyntax

变量 access 指定 -Xdoclint 选项检查的类和成员的最小可见级别。它可以具有以下值(从最可见到最不可见的顺序):publicprotectedpackageprivate

默认的 access 级别是 private

当以 doclint: 为前缀时,group 名称和 all 可以与 @SuppressWarnings 一起使用,以抑制有关正在编译的代码部分中文档注释的警告。

有关这些检查组的更多信息,请参阅 javadoc 命令文档的 DocLint 部分。在 javac 命令中,默认情况下禁用 -Xdoclint 选项。

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

-Xdoclint:all/protected

以下选项启用所有访问级别的所有检查组,但不会检查具有包及更高访问级别(包括包、受保护和公共)的类和成员的 HTML 错误:

-Xdoclint:all,-html/package

-Xdoclint/package:[-]packages(,[-]package)*

启用或禁用特定包中的检查。每个 package 要么是包的限定名称,要么是包名称前缀后跟 .*,它会扩展为给定包的所有子包。每个 package 可以以连字符(-)为前缀,以禁用指定包或包的检查。

有关更多信息,请参阅 javadoc 命令文档的 DocLint 部分。

-Xlint
启用所有推荐的警告。在此版本中,建议启用所有可用的警告。
-Xlint:[-]key(,[-]key)*

提供要启用或禁用的警告,用逗号分隔。在键之前加一个连字符(-)以禁用指定的警告。

key 的支持值为:

  • all:启用所有警告。

  • auxiliaryclass:警告有一个在源文件中隐藏的辅助类,并且从其他文件中使用。

  • cast:警告不必要的强制类型转换的使用。

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

  • deprecation:警告使用已弃用项。

  • dep-ann:警告在 javadoc 中标记为已弃用但没有 @Deprecated 注释的项。

  • divzero:警告整数常量 0 的除法。

  • empty:警告在 if 后的空语句。

  • exports:警告与模块导出相关的问题。

  • fallthrough:警告从一个 switch 语句的一个 case 掉到下一个 case。

  • finally:警告 finally 子句不正常终止。

  • lossy-conversions:警告复合赋值中可能的损失转换。

  • missing-explicit-ctor:警告在导出包中的公共和受保护类中缺少显式构造函数。

  • module:警告与模块系统相关的问题。

  • opens:警告与模块 opens 相关的问题。

  • options:警告与命令行选项使用相关的问题。

  • output-file-clash:警告如果在编译期间覆盖任何输出文件。例如,在不区分大小写的文件系统上可能会发生这种情况。

  • overloads:警告与方法重载相关的问题。

  • overrides:警告与方法覆盖相关的问题。

  • path:警告命令行上的无效路径元素。

  • preview:警告使用预览语言特性。

  • processing:警告与注解处理相关的问题。

  • rawtypes:警告使用原始类型。

  • removal:警告使用已标记为移除的 API。

  • requires-automatic:警告开发人员在 requires 子句中使用自动模块。

  • requires-transitive-automatic:警告关于 requires transitive 中的自动模块。

  • serial:警告不提供序列化版本 ID 的可序列化类。还警告从可序列化元素访问非公共成员。

  • static:警告使用实例访问静态成员。

  • strictfp:警告不必要使用 strictfp 修饰符。

  • synchronization:警告在值类实例上尝试同步。

  • text-blocks:警告文本块缩进中不一致的空格字符。

  • this-escape:警告构造函数在子类初始化之前泄漏 this

  • try:警告与 try 块的使用相关的问题(即,try-with-resources)。

  • unchecked:警告有关未经检查的操作。

  • varargs:警告关于潜在不安全的 vararg 方法。

  • none:禁用所有警告。

除了 allnone 之外,键可以与 @SuppressWarnings 注释一起在要编译的源代码部分中抑制警告。

请参阅 使用 -Xlint 键的示例

-Xmaxerrs number
设置要打印的最大错误数。
-Xmaxwarns number
设置要打印的最大警告数。
-Xpkginfo:[always, legacy, nonempty]

指定 javac 命令如何生成 package-info.java 文件的 package-info.class 文件,使用以下选项之一:

always
为每个 package-info.java 文件生成一个 package-info.class 文件。如果您使用像 Ant 这样的构建系统,该选项可能很有用,因为它会检查每个 .java 文件是否有对应的 .class 文件。
legacy

仅当 package-info.java 包含注解时才生成 package-info.class 文件。如果 package-info.java 只包含注释,则不会生成 package-info.class 文件。

注意:如果 package-info.java 文件中的所有注解都具有 RetentionPolicy.SOURCE,则可能会生成一个空的 package-info.class 文件。

nonempty
仅当 package-info.java 包含具有 RetentionPolicy.CLASSRetentionPolicy.RUNTIME 的注解时才生成 package-info.class 文件。
-Xplugin:name args
指定要运行的插件的名称和可选参数。如果提供了 args,则应该对 nameargs 进行引用或以其他方式转义名称和所有参数之间的空格字符。有关插件的 API 详细信息,请参阅 jdk.compiler/com.sun.source.util.Plugin 的 API 文档。
-Xprefer:[source, newer]

指定在为隐式编译的类找到源文件和类文件时应读取哪个文件,使用以下选项之一。请参阅 搜索模块、包和类型声明

  • -Xprefer:newer:读取类型的源文件或类文件中较新的文件(默认)。

  • -Xprefer:source:读取源文件。当您希望确保任何注解处理器可以访问使用 SOURCE 保留策略声明的注解时,请使用 -Xprefer:source

-Xprint
为调试目的打印指定类型的文本表示。这不执行注解处理或编译。输出的格式可能会更改。
-XprintProcessorInfo
打印处理器被要求处理的注解信息。
-XprintRounds
打印有关初始和后续注解处理轮次的信息。
-Xstdout filename
将编译器消息发送到指定的文件。默认情况下,编译器消息会发送到 System.err

环境变量

CLASSPATH

如果未指定--class-path选项或其任何替代形式,则类路径将默认为CLASSPATH环境变量的值(如果已设置)。但是,建议不设置此环境变量,并在需要时使用--class-path选项为类路径提供显式值。

JDK_JAVAC_OPTIONS

JDK_JAVAC_OPTIONS环境变量的内容,由空格( )或空白字符(\n\t\r\f)分隔,作为传递给javac的命令行参数列表的前缀。

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

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

引用包含空格的参数的示例:

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

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

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

命令行参数文件

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

参数文件中的文件名是相对于当前目录而不是参数文件的位置。这些列表中不允许使用通配符(例如指定*.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

创建名为sources的文件,其中包含以下内容:

MyClass1.java
MyClass2.java
MyClass3.java

然后,运行以下javac命令:

javac @options @sources

带路径的参数文件

参数文件可以具有路径,但文件内部的任何文件名都是相对于当前工作目录而不是path1path2

javac @path1/options @path2/sources

源代码的排列

在Java语言中,类和接口可以组织到包中,包可以组织到模块中。javac期望文件系统目录中源文件的物理排列将反映类组织到包中,包组织到模块中的结构。

通常采用的约定是模块名称和包名称以小写字母开头,类名以大写字母开头。

包的源代码排列

当类和接口组织到一个包中时,该包表示为一个目录,任何子包表示为子目录。

例如:

在目录或子目录中,.java文件表示相应包或子包中的类和接口。

例如:

在某些情况下,将代码拆分为按上述方式结构化的单独目录,并将目录列表指定给javac是方便的。

模块的源代码排列

在Java语言中,模块是为重用而设计的一组包。除了用于类和接口的.java文件外,每个模块还有一个名为module-info.java的源文件,其中:

  1. 声明模块的名称;

  2. 列出模块导出的包(以允许其他模块重用);

  3. 列出模块所需的其他模块(以重用其导出的包)。

当包组织到一个模块中时,模块由表示模块中包的一个或多个目录表示,其中一个目录包含module-info.java文件。可能方便但不是必需的,使用一个单独的目录,以模块命名,包含module-info.java文件以及表示模块中包的目录树(即上述的包层次结构)。模块的源代码排列方式通常由开发环境(IDE)或构建系统采用的约定决定。

例如:

开发环境可能规定模块命名的目录层次结构与javac要读取的源文件之间的某种目录层次结构。

例如:

配置编译

本节描述如何配置javac执行基本编译。

有关在为支持模块的平台版本编译时使用的其他详细信息,请参阅配置模块系统

源文件

如果没有编译错误,则相应的类文件将放在输出目录中。

某些系统可能限制可以放在命令行上的内容量;为了解决这些限制,可以使用参数文件

在为模块编译代码时,还可以间接指定源文件,通过使用--module-m选项。

输出目录

通常会按照包层次结构组织,除非您正在从多个模块编译源代码,此时将按照模块层次结构组织。

编译完成后,如果正在编译一个或多个模块,则可以将输出目录放在Java启动器的模块路径上;否则,可以将输出目录放在Java启动器的类路径上。

预编译代码

要编译的代码可能引用平台提供的库之外的库。如果是这样,必须将这些库放在类路径或模块路径上。如果库代码不在模块中,请将其放在类路径上;如果在模块中,请将其放在模块路径上。

注意:类路径和模块路径的选项并不是互斥的,尽管在为一个或多个模块编译代码时指定类路径并不常见。

额外的源文件

要编译的代码可能引用未在命令行上指定的其他源文件中的类型。如果是这样,您必须将这些源文件放在源路径或模块路径上。您只能指定这些选项中的一个:如果您不是为模块编译代码,或者只编译单个模块的代码,请使用源路径;如果您正在为多个模块编译代码,请使用模块源路径。

如果您想要引用其他源文件中的类型但不希望对其进行编译,请使用-implicit选项。

注意:如果您正在为多个模块编译代码,您必须始终指定一个模块源路径,并且命令行上指定的所有源文件必须位于模块源路径的一个目录中,或者其子目录中。

编译多个源文件的示例

此示例编译Aloha.javaGutenTag.javaHello.javaHi.java源文件,它们位于greetings包中。

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

如果您将greetings.Hi更改为使用横幅实用程序,则该实用程序也需要通过用户类路径访问。

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

配置模块系统

如果要在编译中包含额外的模块,请使用--add-modules选项。当您编译不在模块中的代码,或者在自动模块中的代码引用额外模块的API时,可能需要这样做。

如果要限制编译中的模块集,请使用--limit-modules选项。如果要确保您正在编译的代码能够在安装了有限模块集的系统上运行,这可能很有用。

如果要打破封装并指定额外的包应被视为从模块导出,请使用--add-exports选项。在执行白盒测试时可能会有用;在生产代码中依赖内部API访问是强烈不建议的。

如果要指定额外的包应被视为模块所需,请使用--add-reads选项。在执行白盒测试时可能会有用;在生产代码中依赖内部API访问是强烈不建议的。

您可以使用--patch-module选项将额外内容补丁到任何模块中。有关更多详细信息,请参见[补丁模块]。

搜索模块、包和类型声明

要编译源文件,编译器通常需要有关模块或类型的信息,但声明不在命令行上指定的源文件中。

javac需要每个在源文件中使用、扩展或实现的类或接口的类型信息。这包括源文件中未明确提及但通过继承提供信息的类和接口。

例如,当您创建java.awt.Window的子类时,您还在使用Window的祖先类:java.awt.Containerjava.awt.Componentjava.lang.Object

在为模块编译代码时,编译器还需要有该模块的声明。

成功的搜索可能会产生一个类文件、一个源文件或两者。如果找到并使用了两者,则可以使用-Xprefer选项指示编译器使用哪一个。

如果搜索找到并使用了一个源文件,则默认情况下javac会编译该源文件。可以使用-implicit更改此行为。

直到注解处理完成后,编译器可能不会发现对某些类型信息的需求。当在源文件中找到类型信息且未指定-implicit选项时,编译器会发出警告,指出正在编译该文件而不受注解处理的影响。要禁用警告,要么在命令行上指定文件(以便它将受到注解处理的影响),要么使用-implicit选项指定是否应为这种源文件生成类文件。

javac定位这些类型的声明的方式取决于引用是在模块代码内还是在模块外。

搜索面向包的路径

在搜索由包定位位置组成的路径上的源文件或类文件时,javac将依次检查路径上的每个位置,以查看可能存在文件的情况。特定文件的第一次出现会遮蔽(隐藏)后续出现的同名文件。这种遮蔽不会影响对具有不同名称的文件的搜索。当搜索源文件时,这可能很方便,因为源文件可能分组在不同位置,例如共享代码、特定于平台的代码和生成的代码。当将类文件的替代版本注入到包中时,这也可能很有用,用于调试或其他仪器化目的。但是,这也可能很危险,例如将不兼容的不同版本的库放在类路径上。

搜索面向模块的路径

在扫描任何模块路径以查找任何包或类型声明之前,javac将懒惰地扫描以下路径和位置,以确定将在编译中使用的模块。

对于任何模块,模块的第一次出现在扫描期间完全遮蔽(隐藏)任何后续出现的同名模块。在定位模块时,javac能够确定模块导出的包,并为模块的内容关联每个模块一个面向包的路径。对于任何先前编译的模块,此路径通常是一个目录或提供内部类似目录层次结构的文件的单个条目,例如JAR文件。因此,当搜索在已知由模块导出的包中的类型时,javac可以直接且高效地定位声明。

搜索模块声明

如果模块以前已经编译过,则模块声明位于包层次结构的根目录中名为module-info.class的文件中。

如果模块是当前正在编译的模块之一,则模块声明将位于类输出目录中模块包层次结构的根目录中名为module-info.class的文件中,或者位于源路径或模块源路径中的名为module-info.java的文件中。

在引用不在模块中的类型时搜索声明

当在不在模块中的代码中搜索引用的类型时,javac将在以下位置查找:

在查找类路径和/或源路径上的类型时,如果找到了已编译的类文件和源文件,则默认情况下将使用最近修改的文件。如果源文件更新,则将对其进行编译,并可能覆盖文件的先前编译版本。您可以使用-Xprefer选项来覆盖默认行为。

在模块中引用类型的声明

当在模块中的代码中搜索引用的类型时,javac将检查封闭模块的声明,以确定类型是否在从另一个模块导出且可被封闭模块读取的包中。如果是这样,javac将直接转到该模块的定义以找到所需类型的定义。除非模块是另一个正在编译的模块,否则javac只会查找已编译的类文件。换句话说,javac不会在平台模块或模块路径上查找源文件。

如果引用的类型不在其他可读模块中,javac将检查正在编译的模块以尝试找到类型的声明。javac将按以下方式查找类型的声明:

目录层次结构

javac通常假定源文件和已编译的类文件将组织在文件系统目录层次结构中,或者在支持内部目录层次结构的文件类型中,例如JAR文件。支持三种不同类型的层次结构:包层次结构、模块层次结构和模块源层次结构。

虽然javac对源代码的组织相对宽松,除了期望源代码将组织在一个或多个包层次结构中之外,并且通常可以适应由开发环境和构建工具规定的组织,但Java工具总体上,特别是javac和Java启动器,对已编译的类文件的组织更为严格,并将根据需要组织在包层次结构或模块层次结构中。

这些层次结构的位置通过命令行选项指定给javac,其名称通常以"path"结尾,如--source-path--class-path。另外,作为一般规则,名称中包含单词module的路径选项,如--module-path,用于指定模块层次结构,尽管一些与模块相关的路径选项允许按模块基础指定包层次结构。所有其他路径选项用于指定包层次结构。

包层次结构

在包层次结构中,目录和子目录用于表示包名称的组成部分,类型的源文件或已编译的类文件存储为带有.java.class扩展名的文件,存储在最嵌套的目录中。

例如,在包层次结构中,类com.example.MyClass的源文件将存储在文件com/example/MyClass.java

模块层次结构

在模块层次结构中,第一级目录以层次结构中的模块命名;在每个目录中,模块的内容以包层次结构组织。

例如,在模块层次结构中,名为my.library的模块中名为com.example.MyClass的类型的已编译类文件将存储在my.library/com/example/MyClass.class中。

javac使用的各种输出目录(类输出目录、源输出目录和本机头文件输出目录)在编译多个模块时都将组织在模块层次结构中。

模块源层次结构

虽然每个单独模块的源代码应始终组织在包层次结构中,但将这些层次结构分组到模块源层次结构中可能是方便的。这类似于模块层次结构,不同之处在于在模块源层次结构中,模块目录和源代码的包层次结构的根目录之间可能存在中间目录。

例如,在模块源层次结构中,名为my.library的模块中名为com.example.MyClass的类型的源文件可能存储在文件my.library/src/main/java/com/example/MyClass.java中。

模块源路径选项

--module-source-path选项有两种形式:一种是模块特定形式,其中为每个包含要编译的代码的模块给出包路径,另一种是模块模式形式,其中通过模式指定每个模块的源路径。当涉及的模块数量较少时,通常更简单使用模块特定形式;当模块数量较大且模块以可由模式描述的规则组织时,使用模块模式形式可能更方便。

可以给出多个--module-source-path选项的实例,每个选项使用模块模式形式或模块特定形式,但受以下限制:

如果对于任何模块使用了模块特定形式,则关联的搜索路径将覆盖可能从模块模式形式中推断出的任何路径。

模块特定形式

模块特定形式允许为任何特定模块给出显式搜索路径。该形式为:

路径分隔符字符在Windows上为;,其他情况下为:

注意:这类似于--patch-module选项使用的形式。

模块模式形式

模块模式形式允许对以规则方式组织的任意数量模块的模块源路径进行简洁指定。

模式由以下规则定义,按顺序应用:

修补模块

javac允许使用--patch-module选项将任何内容(无论是源代码还是已编译形式)补丁到任何模块中。您可能希望这样做以编译类的替代实现,以在运行时补丁到JVM中,或者向模块中注入其他类,例如在测试时。

选项的形式为:

路径分隔符字符在Windows上为;,其他情况下为:。为模块给出的路径必须指定包层次结构的根目录以包含模块的内容

对于任何给定模块,该选项最多可以使用一次。路径上的任何内容将隐藏路径后面和修补模块中的同名内容。

当将源代码补丁到多个模块时,还必须使用--module-source-path,以便输出目录组织在模块层次结构中,并能够容纳正在编译的模块的已编译类文件。

注解处理

javac命令直接支持注解处理。

注解处理器的API定义在javax.annotation.processingjavax.lang.model包及其子包中。

注解处理的工作原理

除非使用-proc:none选项禁用注解处理,否则编译器会搜索所有可用的注解处理器。可以使用-processorpath选项指定搜索路径。如果未指定路径,则使用用户类路径。处理器通过名为META-INF/services/javax.annotation.processing.Processor的服务提供者配置文件在搜索路径上定位。这些文件应包含要使用的任何注解处理器的名称,每行列出一个。或者,可以显式指定处理器,使用-processor选项。

在扫描命令行上的源文件和类以确定存在哪些注解后,编译器会查询处理器以确定它们处理哪些注解。找到匹配项后,将调用处理器。处理器可以声明它处理的注解,在这种情况下,不会进一步尝试查找任何处理这些注解的处理器。在声明所有注解之后,编译器不会再搜索其他处理器。

如果任何处理器生成新的源文件,则会发生另一轮注解处理:扫描任何新生成的源文件,并像以前一样处理注解。任何在之前轮次上调用的处理器也会在所有后续轮次上调用。这将持续到不再生成新的源文件为止。

在发生不再生成新源文件的一轮后,最后一次调用注解处理器,以便让它们有机会完成任何剩余工作。最后,除非使用-proc:only选项,否则编译器会编译原始源文件和所有生成的源文件。

如果使用生成额外源文件以包含在编译中的注解处理器,可以指定用于新生成文件的默认模块,用于在没有生成模块声明的情况下使用。在这种情况下,使用--default-module-for-created-files选项。

编译环境和运行时环境。

源文件和先前编译的类文件中的声明由javac在与用于执行javac运行时环境不同的编译环境中进行分析。尽管许多javac选项与Java 启动器的同名选项有意相似,例如--class-path--module-path等,但重要的是要理解,一般来说,javac选项只影响源文件编译的环境,并不影响javac本身的操作。

在使用注解处理器时,编译环境和运行时环境之间的区别是重要的。尽管注解处理器处理存在于编译环境中的元素(声明),但注解处理器本身在运行时环境中执行。如果注解处理器依赖于不在模块中的库,则可以将库与注解处理器本身一起放在处理器路径上。 (请参阅--processor-path选项。)如果注解处理器及其依赖项在模块中,则应改用处理器模块路径。 (请参阅--processor-module-path选项。)当这些不足以时,可能需要进一步配置运行时环境。可以通过两种方式来完成:

  1. 如果从命令行调用javac,则可以通过在选项前加上-J来将选项传递给底层运行时。

  2. 您可以直接启动Java虚拟机的实例,并使用命令行选项和API来配置一个环境,其中可以通过其APIs之一调用javac

为平台的早期版本编译

javac可以编译用于平台其他版本的代码,使用--release选项,或--source/-source--target/-target选项,以及其他选项来指定平台类。

根据所需的平台版本,对某些选项的使用有一些限制。

使用--release选项时,只能使用该版本的支持文档化API;不能使用任何选项来打破封装以访问任何内部类。

APIs

javac编译器可以使用三种不同的方式调用API:

Java编译器API
这提供了调用编译器的最灵活方式,包括能够编译提供在内存缓冲区或其他非标准文件系统中的源文件的能力。
ToolProvider API

可以通过调用ToolProvider.findFirst("javac")来获取javacToolProvider。这将返回一个具有与命令行工具相同功能的对象。

注意:此API不应与javax.tools包中的同名API混淆。

javac Legacy API
该API仅用于向后兼容。所有新代码应使用Java编译器API或ToolProvider API。

注意:在以com.sun.tools.javac开头的包中找到的所有其他类和方法(com.sun.tools.javac的子包)严格属于内部,并随时可能更改。

使用-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 自Java SE 7起,由{@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");
  // 这里没有break语句。
case 2:
  System.out.println("2");
}

如果在编译此代码时使用了-Xlint:fallthrough选项,则编译器会发出关于可能贯穿到情况的警告,并提供有问题情况的行号。

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。当try块退出时,finally块会执行。在此示例中,当控制转移到catch块时,int方法退出。然而,finally块必须执行,因此它会执行,即使控制已转移到方法外部。

options
警告与使用命令行选项相关的问题。请参阅为早期平台版本编译
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 '...'

当编译器遇到varargs方法时,它会将varargs形式参数转换为数组。在方法ClassWithVarargsMethod.varargsMethod中,编译器将varargs形式参数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

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

源文件AnnoProc.java:

import java.util.*;
import javax.annotation.processing.*;
import javax.lang.model.*;
import javax.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();
   }
}

源文件AnnosWithoutProcessors.java:

@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

要解决此问题,您可以如下调用static方法m1

XLintStatic.m1();

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

this-escape

警告构造函数在子类初始化之前泄漏this。例如,这个类:

public class MyClass {
  public MyClass() {
    System.out.println(this.hashCode());
  }
}

生成以下警告:

MyClass.java:3: warning: [this-escape] possible 'this' escape
                         before subclass is fully initialized
    System.out.println(this.hashCode());
                                    ^

当构造函数执行可能导致在构造函数返回之前调用子类方法时,会生成“this”逃逸警告。在这种情况下,子类方法将在未完全初始化的实例上操作。在上面的示例中,覆盖hashCode()以包含自己的字段的MyClass的子类可能会在显示调用时产生不正确的结果。

只有在可能存在超出当前模块(或包,如果没有模块)的子类时才会生成警告。因此,例如,最终和非公共类中的构造函数不会生成警告。

try

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

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

提供了Java语言规范要求的未经检查的转换警告的更多详细信息,例如:

List l = new ArrayList<Number>();
List<String> ls = l;       // 未经检查的警告

在类型擦除期间,类型 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 的定义生成以下警告:

警告: [varargs] 可能由参数化的可变参数类型 T 引起堆污染

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