@Override对Java 5代码中实现的接口方法的注释没有给出编译错误

  • 我有一个遗留的Java 5项目,至少在2017年12月31日之前仍然会保留。
  • 我的默认系统JDK是1.8。
  • 还安装了JDK 1.5,如在Ubuntu 12.04中安装Java 5和https://askubuntu.com/questions/522523/how-to-install-java-1-5-jdk-in-ubuntu中所述 。

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

 compileWithJava5   ${env.JAVA5_HOME} ${java.5.home}/jre/lib ${java.5.libs}/rt.jar${path.separator}${java.5.libs}/jce.jar     org.apache.maven.plugins maven-compiler-plugin  1.5 1.5  ${java.5.bootclasspath}       

设置$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引导类路径。


根据为什么javac在@Override注释上失败 , JDK 1.5将不允许@Override接口的已实现方法允许,仅对超类中存在的重写方法允许。

在此提交中 , @Override注释用于接口的已实现方法,因此这是无效的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 ,但该插件不会查看编译标志 ,只能查看字节代码。)


编辑 :这是我目前在我的POM中所拥有的。

  compileWithLegacyJDK   1.5 ${env.JAVA5_HOME} ${java.home}/jre/lib ${java.libs}/rt.jar${path.separator}${java.libs}/jce.jar     org.apache.maven.plugins maven-compiler-plugin 3.3  ${java.version} ${java.version}  ${java.bootclasspath}  ${java.version} true ${java.home}/bin/javac      

运行

 export JAVA5_HOME=/var/lib/jenkins/tools/hudson.model.JDK/1.5 mvn compile test-compile -P compileWithLegacyJDK 

有关更多详细信息,请参阅下面的接

问题的核心:Maven仍然使用启动它的JDK编译代码。 由于您正在使用JDK 8,它正在使用JDK 8进行编译,并且要使用其他编译器进行编译,您需要使用工具链或指定正确JDK的路径。

建立

要测试此答案,您可以使用以下POM创建一个简单的Maven项目

   4.0.0 test test 1.0-SNAPSHOT    maven-compiler-plugin 3.3  1.5 1.5  /usr/lib/jvm/jdk1.5.0_22/jre/lib/rt.jar       

用一个类编译坐在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构建这个项目,它将编译,尽管设置1.5

为什么要编译?

Maven编译器插件没有错。 javac是罪魁祸首。 设置-source标志不会告诉javac使用此特定JDK版本编译项目。 它指示javac仅接受特定版本的源代码。 来自javac文档 :

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

例如,如果您指定了-source 1.4 ,那么您尝试编译的源代码不能包含generics,因为稍后会将这些内容引入该语言。 该选项强制应用程序的源兼容性。 使用Java 5generics的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应该在这里javac 。 但是nope: Override 有源保留 ,这意味着在编译完成后完全放弃了注释。 这也意味着当交叉编译发生时,注释不再存在; 在使用JDK 8进行编译时,它被丢弃了。正如您所知,这也是Animal Sniffer Plugin (它支持具有预定义JDK版本的自动bootclasspath )之类的工具无法检测到这一点的原因:缺少注释。

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

我怎么能让它编译?

有两种可能的解决方案。

第一个是直接的,通过Compiler Plugin的executable属性指定javac的路径 :

  maven-compiler-plugin 3.3  1.5 1.5  /usr/lib/jvm/jdk1.5.0_22/jre/lib/rt.jar  1.5 true  /usr/lib/jvm/jdk1.5.0_22/bin/javac   

这将设置编译器应与compilerVersion参数一起使用的JDK的实际版本。 这是一种简单的方法,但请注意,它只会更改用于编译的JDK版本。 Maven仍将使用启动它的JDK 8安装来生成Javadoc或运行unit testing,或任何需要JDK安装工具的步骤。

第二种全球方法是使用工具链。 这些将指示Maven使用不同于用于启动mvn的JDK,并且每个Maven插件(或任何知道工具链的插件)将使用此JDK来执行其操作。 编辑您的POM文件以添加maven-toolchains-plugin的以下插件配置:

  maven-toolchains-plugin 1.1    toolchain       1.5     

缺少的成分告诉那些插件,该工具链的配置在哪里。 这是在toolchains.xml文件中完成的,该文件通常位于~/.m2/toolchains.xml 。 从Maven 3.3.1开始,您可以使用--global-toolchains参数定义此文件的位置,但最好将其保留在用户主目录中。 内容如下:

   jdk  1.5   /usr/lib/jvm/jdk1.5.0_22    

这声明了一个jdk类型的工具链,它为JDK 5提供了JDK home的路径。 Maven插件现在将使用此JDK。 实际上,它也将是编译源代码时使用的JDK。

如果您尝试使用此添加的配置再次编译上面的示例项目…您将最终出现错误:

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

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

仅设置target选项并不能保证您的代码实际上在具有指定版本的JRE上运行。 缺点是无意中使用的API只存在于以后的JRE中,这会使您的代码在运行时因链接错误而失败。 要避免此问题,可以配置编译器的引导类路径以匹配目标JRE,也可以使用Animal Sniffer Maven插件validation代码是否使用非预期的API。