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

Ame*_*sse 3 java jdk1.5 cross-compiling maven

POM包含(如/sf/answers/1567929891/中所述):

<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>
Run Code Online (Sandbox Code Playgroud)

$JAVA5_HOME 设置:

• echo $JAVA5_HOME
/usr/lib/jvm/jdk1.5.0_22
Run Code Online (Sandbox Code Playgroud)

据我所知,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) {
    }
}
Run Code Online (Sandbox Code Playgroud)

我跑的时候

mvn clean compile test-compile -P compileWithJava5
Run Code Online (Sandbox Code Playgroud)

我没有在包含@Override注释的类上得到编译错误.我在这里错过了什么?

(已经尝试过:Animal Sniffer Maven插件,但该插件不会查看编译标志,只能查看字节代码.)


编辑:这是我目前在我的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>
Run Code Online (Sandbox Code Playgroud)

运行

export JAVA5_HOME=/var/lib/jenkins/tools/hudson.model.JDK/1.5
mvn compile test-compile -P compileWithLegacyJDK
Run Code Online (Sandbox Code Playgroud)

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

Tun*_*aki 7

问题的核心:Maven仍在使用启动它的JDK编译代码.由于您正在使用JDK 8,它正在使用JDK 8进行编译,并且要使用其他编译器进行编译,因此您需要使用工具链或指定正确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>
Run Code Online (Sandbox Code Playgroud)

用一个类编译坐下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");
    }
}
Run Code Online (Sandbox Code Playgroud)

这看起来像是一个配置为使用JDK 5的标准Maven项目.请注意,该类使用@Override实现接口的方法.在Java 6之前不允许这样做.

如果您尝试使用在JDK 8下运行的Maven构建此项目,它将进行编译,尽管设置<source>1.5</source>.

为什么编译?

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

-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具有源保留,这意味着编译发生后的注释是完全丢弃.这也意味着当交叉编译发生时,注释不再存在; 在使用JDK 8进行编译时,它被丢弃.正如您所知,这也是为什么像Animal Sniffer Plugin这样的工具(它能够自动bootclasspath使用预定义的JDK版本)不能检测到这一点:缺少注释.

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

我怎么能让它编译?

有两种可能的解决方案.

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

<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>
Run Code Online (Sandbox Code Playgroud)

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

第二种全球方法是使用工具链.这些将指示Maven使用与用于启动的JDK不同的JDK mvn,并且每个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>
Run Code Online (Sandbox Code Playgroud)

缺少的成分是告诉那些插件,该工具链的配置在哪里.这是在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>
Run Code Online (Sandbox Code Playgroud)

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

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

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