【问题标题】:@Override annotation on implemented method of interface in Java 5 code doesn't give a compilation errorJava 5代码中接口的实现方法上的@Override注解不会产生编译错误
【发布时间】:2016-10-14 12:12:06
【问题描述】:

POM 包含(如https://stackoverflow.com/a/22398998/766786 中所述):

<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 伪装成 JDK 1.5 并使用 Java 5 引导类路径。


根据Why is javac failing on @Override annotationJDK 1.5 将不允许@Override 用于接口 的已实现方法,仅用于超类中存在的重写方法

this commit 中,@Override 注释用于interface 的实现方法,因此这是无效的Java 5 代码:

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 注释的类上没有出现编译错误。我在这里错过了什么?

(已经尝试过:Animal Sniffer Maven 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

有关详细信息,请参阅下面接受的答案。

【问题讨论】:

  • 请显示相关代码
  • 您误会了sourcetarget。你需要设置一个引导类路径来检测这样的问题:stackoverflow.com/a/25273329/1743880
  • @Jens 我已经添加了一个指向相关代码的链接,以及 POM 中告诉 Java 表现得像 Java 5 的部分。我希望现在问题更清楚了。
  • 嗯,这很奇怪。我会尝试设置这样的东西,并重现结果
  • 随意克隆问题中链接的 repo。 @Override 注释已添加到 TableEventTest.java 中,并在手动审查代码后在稍后的提交中删除。 CI 应该早点发现它。

标签: java maven cross-compiling jdk1.5


【解决方案1】:

问题的核心:Maven 仍在使用启动它的 JDK 编译您的代码。由于您使用的是JDK 8,因此它是使用JDK 8编译的,并且要使用其他编译器进行编译,您需要使用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>

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 5 的标准 Maven 项目。请注意,该类在实现接口的方法上使用了@Override。这在 Java 6 之前是不允许的。

如果您尝试使用在 JDK 8 下运行的 Maven 构建此项目,尽管设置了 &lt;source&gt;1.5&lt;/source&gt;,它仍会编译。

为什么会编译?

Maven 编译器插件没有问题。 javac 是罪魁祸首。设置-source 标志不会告诉javac 使用这个特定的JDK 版本编译您的项目。它指示javac 只接受特定版本的源代码。来自javac documentation

-source release:指定接受的源代码版本。

例如,如果您指定了-source 1.4,那么您尝试编译的源代码不能包含泛型,因为这些是后来被引入该语言的。该选项强制执行应用程序的源兼容性。使用 Java 5 泛型的 Java 应用程序与使用 JDK 4 编译器的 Java 4 程序源不兼容。同样,使用 Java 8 lambda 表达式的应用程序与 JDK 6 编译器的源代码不兼容。

在这种情况下,@Override 是 Java 5 中已经存在的注解。但是,它的语义在 Java 6 中发生了变化。因此,使用@Override 的代码,无论是在实现接口的方法上还是在实现接口的方法上,源代码与 Java 5 程序兼容。因此,在此类上运行带有 -source 1.5 的 JDK 8 不会失败。

为什么会运行?

在第二个参数上:target。同样,这不是 Maven 编译器的问题,而是javac 的问题。 -source 标志强制执行与旧版本的源代码兼容性,-target 强制执行与旧版本的二进制兼容性。该标志告诉javac 生成与旧版JVM 兼容的字节码。它不会告诉javac 检查编译后的代码是否可以在较旧的 JVM 版本上实际运行。为此,您需要设置一个bootclasspath,它将使用指定的 JDK 交叉编译您的代码。

显然,实现接口的方法上的@Override 不能在Java 5 VM 上运行,所以javac 应该在这里咆哮。但是没有:Overridesource retention,这意味着在编译发生后注释被完全丢弃。这也意味着当交叉编译发生时,注释不再存在;它在使用 JDK 8 编译时被丢弃。正如您所发现的,这也是为什么像 Animal Sniffer Plugin(它启用具有预定义 JDK 版本的自动 bootclasspath)之类的工具不会检测到这一点的原因:注释丢失。

总之,您可以使用在 JDK 8 上运行的 mvn clean package 打包上面的示例应用程序,并在 Java 5 JVM 上运行它而不会遇到任何问题。它将打印"foo"

我怎样才能让它编译?

有两种可能的解决方案。

第一个,直接的,is to specify the path to javac 通过 Compiler Plugin 的 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。一旦我在 Jenkins 服务器上完成了这个,我将接受答案并奖励你。我要感谢你写得很好的答案!
  • 我对@Amedee 做了一个小编辑,因为我记得您可以配置编译器以使用特定的 JDK 版本,而不是所有插件(如 Javadoc)。实际上,使用工具链会更干净,因为它确保在整个构建过程中使用相同的 JDK。
  • 我选择配置 just 编译器,因为 a) 不需要对 toolchains.xml 进行任何更改(任何用户都应该能够克隆 repo 并构建,而无需对其进行太多配置系统)b)我实际上想要在构建的其他部分使用JDK 8,例如javadocs。但我会记住它,也许其他人可以使用它。我将奖励您赏金,因为这是一个写得很好的答案,值得成为一个规范的答案。除了你的答案,我仍然把它放在一个单独的配置文件中,它只在 CI 服务器上运行(就像我原来的问题一样)。
  • 有趣的是,就在我设置奖金之后,我自己弄清楚了你写的关于为什么会编译?为什么会运行? ,感谢这篇博文:kohsuke.org/2012/01/27/override-and-interface。感谢你,我了解了工具链。
【解决方案2】:

当您使用JDK 1.8 时将target 设置为1.5 并不能保证您的代码将在1.5 上运行,如maven-compiler-plugin 的文档中所述。

仅仅设置target 选项并不能保证您的代码 实际上在具有指定版本的 JRE 上运行。陷阱是 意外使用仅存在于后来的 JRE 中的 API,这将 使您的代码在运行时因链接错误而失败。为了避免这种情况 问题,您可以配置编译器的引导类路径以匹配 目标 JRE 或使用 Animal Sniffer Maven Plugin 来验证您的 代码不使用非预期的 API。

【讨论】:

  • 我将尝试使用 Animal Sniffer Maven 插件,如果它像宣传的那样工作,那么我会接受这个答案。我想避免交叉编译,因为我们是一个开源项目,我不想告诉我们的用户他们应该安装多个 JDK 才能编译我们的产品。
  • 上次更新是 8 个月前,对于像成熟的 Maven 插件这样相对稳定的东西来说已经足够了。
  • 我不确定你的问题是什么@AmedeeVanGasse。您是否希望在实现接口的方法上具有 @Override 的代码进行编译?您的问题是问为什么它似乎 可以用 Java 5 编译,但答案是,它没有,Animal Sniffer Plugin 会引发错误(就像 javac 设置 @ 987654332@)。如果你想编译和运行这样的代码,你必须使用 JDK >= 6。
  • 好的,Animal Sniffer 非常有趣,但是它并没有检测到这个特定的@Override 事物,正如本期提到的:github.com/mojohaus/animal-sniffer/issues/14。不幸的是,这意味着我将不得不做丑陋的交叉编译黑客。
  • @Tunaki "您是否希望代码在实现接口的方法上具有 @Override 以进行编译?" --> 不,我愿意 想要编译。我想得到一个编译错误。此特定项目必须与 JDK==1.5 保持兼容。
猜你喜欢
  • 1970-01-01
  • 2012-06-12
  • 2011-06-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多