Java 5 代码中实现的接口方法上的@Override 注释不会给出编译错误

@Override annotation on implemented method of interface in Java 5 code doesn't give a compilation error

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