java - Java 5代码中接口(interface)的已实现方法的@Override注释未给出编译错误

标签 java maven cross-compiling jdk1.5

  • 我有一个旧版Java 5项目,该项目将一直保留到至少2017年12月31日。
  • 我的默认系统JDK是1.8。
  • Install Java 5 in Ubuntu 12.04https://askubuntu.com/questions/522523/how-to-install-java-1-5-jdk-in-ubuntu中所述,还安装了
  • JDK 1.5。

  • 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 annotation的规定, JDK 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
    

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

    最佳答案

    问题的核心: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来构建该项目,则尽管设置了<source>1.5</source>,它仍将编译。

    为什么要编译?

    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应该在此处吠叫。但是没有: Override 具有source 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插件的 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不同的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。

    而且,如果您尝试使用此添加的配置再次编译上面的示例项目,您将最终得到以下错误:

    方法不会覆盖其超类中的方法

    关于java - Java 5代码中接口(interface)的已实现方法的@Override注释未给出编译错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40042878/

    相关文章:

    maven - 为 Spark 包添加解析器

    c++ - 交叉编译: link to library only available on target system

    java - 用定制的任务调度程序替换 Java 中的任务调度程序

    java - 我想扩展ArrayList来实现但无法访问element Data private var

    java - 无法从 XSD 生成类

    c++ - 使用 gcc 的工具链中的 sysroot 头文件优先于头文件

    c - busybox 中的 L_* 宏是什么?

    java - UTF-8 和 ISO 8859-1 之间的转换 :

    java - 为什么 'create'是异步的?

    java - Eclipse Maven 2 安装