Java 5 代码中实现的接口方法上的@Override 注释不会给出编译错误
@Override annotation on implemented method of interface in Java 5 code doesn't give a compilation error
- 我有一个遗留的 Java 5 项目,该项目仍将至少维护到 2017 年 12 月 31 日。
- 我的默认系统 JDK 是 1.8。
- JDK 1.5也已安装,如Install Java 5 in Ubuntu 12.04 and https://askubuntu.com/questions/522523/how-to-install-java-1-5-jdk-in-ubuntu.
所述
POM 包含(如 中所述):
<profile>
<id>compileWithJava5</id>
<!--
NOTE
Make sure to set the environment variable JAVA5_HOME
to your JDK 1.5 HOME when using this profile.
-->
<properties>
<java.5.home>${env.JAVA5_HOME}</java.5.home>
<java.5.libs>${java.5.home}/jre/lib</java.5.libs>
<java.5.bootclasspath>${java.5.libs}/rt.jar${path.separator}${java.5.libs}/jce.jar</java.5.bootclasspath>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.5</source>
<target>1.5</target>
<compilerArguments>
<bootclasspath>${java.5.bootclasspath}</bootclasspath>
</compilerArguments>
</configuration>
</plugin>
</plugins>
</build>
</profile>
$JAVA5_HOME
已设置:
• echo $JAVA5_HOME
/usr/lib/jvm/jdk1.5.0_22
据我了解 Java+Maven 的魔法,这应该是 maven-compiler-plugin
指示 JDK 1.8 伪装成 [=73] 的有效咒语=] 1.5 并使用 Java 5 引导 class 路径。
根据 Why is javac failing on @Override annotation,JDK 1.5 将不允许 @Override
实现 接口的方法,仅在 super class.
中出现的重写方法
在this commit the @Override
annotation is used on the implemented method of an interface,所以这个是无效的Java5码:
private static class DummyEvent implements PdfPTableEvent {
@Override
public void tableLayout(PdfPTable table, float[][] widths, float[] heights, int headerRows, int rowStart, PdfContentByte[] canvases) {
}
}
当我运行
mvn clean compile test-compile -P compileWithJava5
我没有在包含 @Override
注释的 class 上收到编译错误。我在这里错过了什么?
(已经试过:Animal Sniffer Maven Plugin, but that plugin doesn't look at compilation flags,只在字节码处。)
编辑:这是我目前在 POM 中的内容。
<profile>
<id>compileWithLegacyJDK</id>
<!--
NOTE
Make sure to set the environment variable JAVA5_HOME
to your JDK 1.5 HOME when using this profile.
-->
<properties>
<java.version>1.5</java.version>
<java.home>${env.JAVA5_HOME}</java.home>
<java.libs>${java.home}/jre/lib</java.libs>
<java.bootclasspath>${java.libs}/rt.jar${path.separator}${java.libs}/jce.jar</java.bootclasspath>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<compilerArguments>
<bootclasspath>${java.bootclasspath}</bootclasspath>
</compilerArguments>
<compilerVersion>${java.version}</compilerVersion>
<fork>true</fork>
<executable>${java.home}/bin/javac</executable>
</configuration>
</plugin>
</plugins>
</build>
</profile>
运行 和
export JAVA5_HOME=/var/lib/jenkins/tools/hudson.model.JDK/1.5
mvn compile test-compile -P compileWithLegacyJDK
有关详细信息,请参阅下面接受的答案。
当您使用 JDK 1.8
时将 target
设置为 1.5
并不能保证您的代码将在 1.5
上运行,如 [=17= 的文档中所述].
Merely setting the target
option does not guarantee that your code
actually runs on a JRE with the specified version. The pitfall is
unintended usage of APIs that only exist in later JREs which would
make your code fail at runtime with a linkage error. To avoid this
issue, you can either configure the compiler's boot classpath to match
the target JRE or use the Animal Sniffer Maven Plugin to verify your
code doesn't use unintended APIs.
问题的核心:Maven 仍在使用启动它的 JDK 编译您的代码。因为你用的是JDK8,所以是用JDK8编译的,要用别的编译器编译,需要用toolchains或者指定右边的路径JDK.
设置
要测试这个答案,您可以创建一个包含以下 POM 的简单 Maven 项目
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>test</groupId>
<artifactId>test</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>1.5</source>
<target>1.5</target>
<compilerArguments>
<bootclasspath>/usr/lib/jvm/jdk1.5.0_22/jre/lib/rt.jar</bootclasspath>
</compilerArguments>
</configuration>
</plugin>
</plugins>
</build>
</project>
有一个 class 编译坐在 src/main/java/test
下,是:
package test;
interface I {
void foo();
}
public class Main implements I {
public static void main(String[] args) {
new Main().foo();
}
@Override
public void foo() {
System.out.println("foo");
}
}
这看起来像是配置为使用 JDK 的标准 Maven 项目 5. 请注意,class 在实现接口的方法上使用了 @Override
。在 Java 6.
之前这是不允许的
如果您尝试在 JDK 8 下使用 Maven 运行ning 构建此项目,它将编译,尽管设置 <source>1.5</source>
.
为什么编译?
Maven 编译器插件没有问题。 javac
是罪魁祸首。设置 -source
标志不会告诉 javac
使用此特定 JDK 版本编译您的项目。它指示 javac
只接受特定版本的源代码。来自 javac
documentation:
-source release
: Specifies the version of source code accepted.
例如,如果您指定了 -source 1.4
,那么您尝试编译的源代码不能包含泛型,因为泛型是后来引入到语言中的。该选项强制应用程序的源兼容性。使用 Java 5 泛型的 Java 应用程序与使用 JDK 4 编译器的 Java 4 程序源代码不兼容。同样,使用 Java 8 lambda 表达式的应用程序与 JDK 6 编译器的源代码不兼容。
在这种情况下,@Override
是 Java 5 中已经存在的注释。但是,它的语义在 Java 6 中发生了变化。因此,使用 [=16= 的代码],无论它是否在实现接口的方法上,都与 Java 5 程序源代码兼容。因此,运行在 class 上用 -source 1.5
设置 JDK 8 不会失败。
为什么 运行?
进入第二个参数:target
。同样,这不是 Maven 编译器的问题,而是 javac
的问题。 -source
标志强制与旧版本兼容,而 -target
强制与旧版本二进制兼容。此标志告诉 javac
生成与旧 JVM 版本兼容的字节码。它不会告诉 javac
检查编译后的代码实际上是否可以 运行 使用旧的 JVM 版本。为此,您需要设置一个 bootclasspath
,它将使用指定的 JDK.
交叉编译您的代码
显然,@Override
在实现接口的方法上不能在 Java 5 VM 上 运行,因此 javac
应该在这里咆哮。但是不会:Override
has source retention, meaning that the annotation is completely discarded after compilation has happened. Which also means that when cross-compilation is happening, the annotation isn't there anymore; it was discarded when compiling with JDK 8. As you found out, this is also why tools like the Animal Sniffer Plugin(启用带有预定义 JDK 版本的自动 bootclasspath
)不会检测到这一点:缺少注释。
总而言之,您可以在 JDK 8 上使用 mvn clean package
运行ning 打包上面的示例应用程序,并且 运行 它不会在 Java 5 虚拟机。它将打印 "foo"
.
我怎样才能不编译?
有两种可能的解决方案。
Compiler Plugin的第一个,直接的is to specify the path to javac
through the executable
属性:
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>1.5</source>
<target>1.5</target>
<compilerArguments>
<bootclasspath>/usr/lib/jvm/jdk1.5.0_22/jre/lib/rt.jar</bootclasspath>
</compilerArguments>
<compilerVersion>1.5</compilerVersion>
<fork>true</fork>
<!-- better to have that in a property in the settings, or an environment variable -->
<executable>/usr/lib/jvm/jdk1.5.0_22/bin/javac</executable>
</configuration>
</plugin>
这会设置编译器应与 compilerVersion
参数一起使用的 JDK 的实际版本。这是一个简单的方法,但请注意,它只更改了 用于编译 的 JDK 版本。 Maven 仍将使用启动它的 JDK 8 安装来生成 Javadoc 或 运行 单元测试,或任何需要工具的步骤 JDK 安装。
第二种全局方法是使用工具链。这些将指示 Maven 使用与用于启动 mvn
的不同的 JDK,然后每个 Maven 插件(或任何可识别工具链的插件)将使用此 JDK 来执行它们的手术。编辑您的 POM 文件以添加 maven-toolchains-plugin
:
的以下插件配置
<plugin>
<artifactId>maven-toolchains-plugin</artifactId>
<version>1.1</version>
<executions>
<execution>
<goals>
<goal>toolchain</goal>
</goals>
</execution>
</executions>
<configuration>
<toolchains>
<jdk>
<version>1.5</version>
</jdk>
</toolchains>
</configuration>
</plugin>
缺少的成分是告诉那些插件该工具链的配置在哪里。这是在 toolchains.xml
文件中完成的,通常在 ~/.m2/toolchains.xml
中。从 Maven 3.3.1 开始,您可以使用 --global-toolchains
参数定义此文件的位置,但最好将其保存在用户主目录中。内容为:
<toolchains>
<toolchain>
<type>jdk</type>
<provides>
<version>1.5</version>
</provides>
<configuration>
<jdkHome>/usr/lib/jvm/jdk1.5.0_22</jdkHome>
</configuration>
</toolchain>
</toolchains>
这声明了一个 jdk
类型的工具链,提供 JDK 5 到 JDK 主页的路径。 Maven 插件现在将使用这个 JDK。实际上,它也将是编译源代码时使用的JDK。
如果您尝试使用此添加的配置再次编译上面的示例项目...您最终会遇到错误:
method does not override a method from its superclass
- 我有一个遗留的 Java 5 项目,该项目仍将至少维护到 2017 年 12 月 31 日。
- 我的默认系统 JDK 是 1.8。
- JDK 1.5也已安装,如Install Java 5 in Ubuntu 12.04 and https://askubuntu.com/questions/522523/how-to-install-java-1-5-jdk-in-ubuntu. 所述
POM 包含(如 中所述):
<profile>
<id>compileWithJava5</id>
<!--
NOTE
Make sure to set the environment variable JAVA5_HOME
to your JDK 1.5 HOME when using this profile.
-->
<properties>
<java.5.home>${env.JAVA5_HOME}</java.5.home>
<java.5.libs>${java.5.home}/jre/lib</java.5.libs>
<java.5.bootclasspath>${java.5.libs}/rt.jar${path.separator}${java.5.libs}/jce.jar</java.5.bootclasspath>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.5</source>
<target>1.5</target>
<compilerArguments>
<bootclasspath>${java.5.bootclasspath}</bootclasspath>
</compilerArguments>
</configuration>
</plugin>
</plugins>
</build>
</profile>
$JAVA5_HOME
已设置:
• echo $JAVA5_HOME
/usr/lib/jvm/jdk1.5.0_22
据我了解 Java+Maven 的魔法,这应该是 maven-compiler-plugin
指示 JDK 1.8 伪装成 [=73] 的有效咒语=] 1.5 并使用 Java 5 引导 class 路径。
根据 Why is javac failing on @Override annotation,JDK 1.5 将不允许 @Override
实现 接口的方法,仅在 super class.
在this commit the @Override
annotation is used on the implemented method of an interface,所以这个是无效的Java5码:
private static class DummyEvent implements PdfPTableEvent {
@Override
public void tableLayout(PdfPTable table, float[][] widths, float[] heights, int headerRows, int rowStart, PdfContentByte[] canvases) {
}
}
当我运行
mvn clean compile test-compile -P compileWithJava5
我没有在包含 @Override
注释的 class 上收到编译错误。我在这里错过了什么?
(已经试过:Animal Sniffer Maven Plugin, but that plugin doesn't look at compilation flags,只在字节码处。)
编辑:这是我目前在 POM 中的内容。
<profile>
<id>compileWithLegacyJDK</id>
<!--
NOTE
Make sure to set the environment variable JAVA5_HOME
to your JDK 1.5 HOME when using this profile.
-->
<properties>
<java.version>1.5</java.version>
<java.home>${env.JAVA5_HOME}</java.home>
<java.libs>${java.home}/jre/lib</java.libs>
<java.bootclasspath>${java.libs}/rt.jar${path.separator}${java.libs}/jce.jar</java.bootclasspath>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<compilerArguments>
<bootclasspath>${java.bootclasspath}</bootclasspath>
</compilerArguments>
<compilerVersion>${java.version}</compilerVersion>
<fork>true</fork>
<executable>${java.home}/bin/javac</executable>
</configuration>
</plugin>
</plugins>
</build>
</profile>
运行 和
export JAVA5_HOME=/var/lib/jenkins/tools/hudson.model.JDK/1.5
mvn compile test-compile -P compileWithLegacyJDK
有关详细信息,请参阅下面接受的答案。
当您使用 JDK 1.8
时将 target
设置为 1.5
并不能保证您的代码将在 1.5
上运行,如 [=17= 的文档中所述].
Merely setting the
target
option does not guarantee that your code actually runs on a JRE with the specified version. The pitfall is unintended usage of APIs that only exist in later JREs which would make your code fail at runtime with a linkage error. To avoid this issue, you can either configure the compiler's boot classpath to match the target JRE or use the Animal Sniffer Maven Plugin to verify your code doesn't use unintended APIs.
问题的核心:Maven 仍在使用启动它的 JDK 编译您的代码。因为你用的是JDK8,所以是用JDK8编译的,要用别的编译器编译,需要用toolchains或者指定右边的路径JDK.
设置
要测试这个答案,您可以创建一个包含以下 POM 的简单 Maven 项目
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>test</groupId>
<artifactId>test</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>1.5</source>
<target>1.5</target>
<compilerArguments>
<bootclasspath>/usr/lib/jvm/jdk1.5.0_22/jre/lib/rt.jar</bootclasspath>
</compilerArguments>
</configuration>
</plugin>
</plugins>
</build>
</project>
有一个 class 编译坐在 src/main/java/test
下,是:
package test;
interface I {
void foo();
}
public class Main implements I {
public static void main(String[] args) {
new Main().foo();
}
@Override
public void foo() {
System.out.println("foo");
}
}
这看起来像是配置为使用 JDK 的标准 Maven 项目 5. 请注意,class 在实现接口的方法上使用了 @Override
。在 Java 6.
如果您尝试在 JDK 8 下使用 Maven 运行ning 构建此项目,它将编译,尽管设置 <source>1.5</source>
.
为什么编译?
Maven 编译器插件没有问题。 javac
是罪魁祸首。设置 -source
标志不会告诉 javac
使用此特定 JDK 版本编译您的项目。它指示 javac
只接受特定版本的源代码。来自 javac
documentation:
-source release
: Specifies the version of source code accepted.
例如,如果您指定了 -source 1.4
,那么您尝试编译的源代码不能包含泛型,因为泛型是后来引入到语言中的。该选项强制应用程序的源兼容性。使用 Java 5 泛型的 Java 应用程序与使用 JDK 4 编译器的 Java 4 程序源代码不兼容。同样,使用 Java 8 lambda 表达式的应用程序与 JDK 6 编译器的源代码不兼容。
在这种情况下,@Override
是 Java 5 中已经存在的注释。但是,它的语义在 Java 6 中发生了变化。因此,使用 [=16= 的代码],无论它是否在实现接口的方法上,都与 Java 5 程序源代码兼容。因此,运行在 class 上用 -source 1.5
设置 JDK 8 不会失败。
为什么 运行?
进入第二个参数:target
。同样,这不是 Maven 编译器的问题,而是 javac
的问题。 -source
标志强制与旧版本兼容,而 -target
强制与旧版本二进制兼容。此标志告诉 javac
生成与旧 JVM 版本兼容的字节码。它不会告诉 javac
检查编译后的代码实际上是否可以 运行 使用旧的 JVM 版本。为此,您需要设置一个 bootclasspath
,它将使用指定的 JDK.
显然,@Override
在实现接口的方法上不能在 Java 5 VM 上 运行,因此 javac
应该在这里咆哮。但是不会:Override
has source retention, meaning that the annotation is completely discarded after compilation has happened. Which also means that when cross-compilation is happening, the annotation isn't there anymore; it was discarded when compiling with JDK 8. As you found out, this is also why tools like the Animal Sniffer Plugin(启用带有预定义 JDK 版本的自动 bootclasspath
)不会检测到这一点:缺少注释。
总而言之,您可以在 JDK 8 上使用 mvn clean package
运行ning 打包上面的示例应用程序,并且 运行 它不会在 Java 5 虚拟机。它将打印 "foo"
.
我怎样才能不编译?
有两种可能的解决方案。
Compiler Plugin的第一个,直接的is to specify the path to javac
through the executable
属性:
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>1.5</source>
<target>1.5</target>
<compilerArguments>
<bootclasspath>/usr/lib/jvm/jdk1.5.0_22/jre/lib/rt.jar</bootclasspath>
</compilerArguments>
<compilerVersion>1.5</compilerVersion>
<fork>true</fork>
<!-- better to have that in a property in the settings, or an environment variable -->
<executable>/usr/lib/jvm/jdk1.5.0_22/bin/javac</executable>
</configuration>
</plugin>
这会设置编译器应与 compilerVersion
参数一起使用的 JDK 的实际版本。这是一个简单的方法,但请注意,它只更改了 用于编译 的 JDK 版本。 Maven 仍将使用启动它的 JDK 8 安装来生成 Javadoc 或 运行 单元测试,或任何需要工具的步骤 JDK 安装。
第二种全局方法是使用工具链。这些将指示 Maven 使用与用于启动 mvn
的不同的 JDK,然后每个 Maven 插件(或任何可识别工具链的插件)将使用此 JDK 来执行它们的手术。编辑您的 POM 文件以添加 maven-toolchains-plugin
:
<plugin>
<artifactId>maven-toolchains-plugin</artifactId>
<version>1.1</version>
<executions>
<execution>
<goals>
<goal>toolchain</goal>
</goals>
</execution>
</executions>
<configuration>
<toolchains>
<jdk>
<version>1.5</version>
</jdk>
</toolchains>
</configuration>
</plugin>
缺少的成分是告诉那些插件该工具链的配置在哪里。这是在 toolchains.xml
文件中完成的,通常在 ~/.m2/toolchains.xml
中。从 Maven 3.3.1 开始,您可以使用 --global-toolchains
参数定义此文件的位置,但最好将其保存在用户主目录中。内容为:
<toolchains>
<toolchain>
<type>jdk</type>
<provides>
<version>1.5</version>
</provides>
<configuration>
<jdkHome>/usr/lib/jvm/jdk1.5.0_22</jdkHome>
</configuration>
</toolchain>
</toolchains>
这声明了一个 jdk
类型的工具链,提供 JDK 5 到 JDK 主页的路径。 Maven 插件现在将使用这个 JDK。实际上,它也将是编译源代码时使用的JDK。
如果您尝试使用此添加的配置再次编译上面的示例项目...您最终会遇到错误:
method does not override a method from its superclass