@Override对Java 5代码中实现的接口方法的注释没有给出编译错误 [英] @Override annotation on implemented method of interface in Java 5 code doesn't give a compilation error

查看:169
本文介绍了@Override对Java 5代码中实现的接口方法的注释没有给出编译错误的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述



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

 < profile> 
< id> compileWithJava5< / id>
<! -
注意
确保在使用此配置文件时将环境变量JAVA5_HOME
设置为JDK 1.5 HOME。
- >
< 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-的有效咒语插件指示JDK 1.8假装是JDK 1.5并使用Java 5启动类路径。






根据为什么javac在@Override注释上失败 JDK 1.5 不允许 @Override 关于接口的已实现方法,仅适用于中存在的重写方法超级



此提交 @Override 注释用于实现的方法一个接口,所以这是无效的Java 5代码:

 私有静态类DummyEvent实现PdfPTableEvent {

@Override
public void tableLayout(PdfPTable table,float [] [] widths,float [] height,int headerRows,int rowStart,PdfContentByte [] canvases){
}
}

当我跑步时

  mvn clean compile test-compile -P compileWithJava5 

我没有得到包含 @Override 注释的类的编译错误。我在这里缺少什么?



(已经尝试过:动物嗅探器Maven插件,但该插件不查看编译标志,只查看字节代码。)






编辑:这是我目前在POM中的内容。

 < profile> 
< id> compileWithLegacyJDK< / id>
<! -
注意
确保在使用此配置文件时将环境变量JAVA5_HOME
设置为JDK 1.5 HOME。
- >
< 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进行编译,并且要使用其他编译器进行编译,因此您需要使用工具链或指定右侧JDK的路径。



设置



要测试这个答案,您可以使用以下POM进行简单的Maven项目

 <?xml版本=1.0encoding =UTF-8?> 
< project xmlns =http://maven.apache.org/POM/4.0.0xmlns:xsi =http://www.w3.org/2001/XMLSchema-instancexsi: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下编译一个类,是:

 包裹测试; 

interface I {
void foo();
}
公共类Main实现I {
public static void main(String [] args){
new Main()。foo();
}

@Override
public void foo(){
System.out.println(foo);
}
}

这看起来像是一个配置为使用JDK的标准Maven项目5.请注意,该类在实现接口的方法上使用 @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 应该在这里咆哮。但是没有: 覆盖 源保留,意味着在编译完成后完全丢弃注释。这也意味着当交叉编译发生时,注释不再存在;在使用JDK 8进行编译时,它被丢弃了。正如您所知,这也是动物嗅探器插件(使用预定义的JDK版本启用自动 bootclasspath )将无法检测到:缺少注释。 / p>

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



如何使编译?



有两种可能的解决方案。



第一个,直接的,是指定 javac的路径 通过 编译器插件的可执行 属性:

 < 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>
<! - 最好在设置中的属性或环境变量中使用 - >>
< 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>
< offers>
< version> 1.5< / version>
< /提供>
< configuration>
< jdkHome> /usr/lib/jvm/jdk1.5.0_22< / jdkHome>
< / configuration>
< / toolchain>
< / toolchains>

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



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


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



The POM contains (as described in 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 is set:

• echo $JAVA5_HOME
/usr/lib/jvm/jdk1.5.0_22

As far as I understand the magic that is Java+Maven, this should be a valid incantation of the maven-compiler-plugin to instruct JDK 1.8 to pretend to be JDK 1.5 and use the Java 5 boot classpath.


According to Why is javac failing on @Override annotation, JDK 1.5 will not allow @Override on implemented methods of an interface, only on overridden methods present in a super class.

In this commit the @Override annotation is used on the implemented method of an interface, so this is invalid Java 5 code:

private static class DummyEvent implements PdfPTableEvent {

    @Override
    public void tableLayout(PdfPTable table, float[][] widths, float[] heights, int headerRows, int rowStart, PdfContentByte[] canvases) {
    }
}

When I run

mvn clean compile test-compile -P compileWithJava5

I don't get a compilation error on the class that contains the @Override annotation. What am I missing here?

(Already tried: Animal Sniffer Maven Plugin, but that plugin doesn't look at compilation flags, only at the byte code.)


EDIT: This is what I currently have in my 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 with

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

See accepted answer below for more details.

解决方案

The core of the issue: Maven is still compiling your code with the JDK with which it is launched. Since you're using JDK 8, it is compiling with JDK 8, and to compile with another compiler, you need to use toolchains or specify the path to the right JDK.

Set up

To test this answer, you can have a simple Maven project with the following POM

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

with a single class to compile sitting under src/main/java/test, being:

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");
    }
}

This looks like a standard Maven project configured to use JDK 5. Notice that the class uses @Override on a method implementing an interface. This was not allowed before Java 6.

If you try to build this project with Maven running under JDK 8, it will compile, despite setting <source>1.5</source>.

Why does it compile?

The Maven Compiler Plugin is not at fault. javac is to blame. Setting the -source flag does not tell javac to compile your project with this specific JDK version. It instructs javac to accept only a specific version of source code. From javac documentation:

-source release: Specifies the version of source code accepted.

For example, if you specified -source 1.4, then the source code you're trying to compile cannot contain generics, since those were introduced to the language later. The option enforces the source compatibility of your application. A Java application that uses Java 5 generics is not source compatible with a Java 4 program using a JDK 4 compiler. In the same way, an application using Java 8 lambda expressions is not source compatible to a JDK 6 compiler.

In this case, @Override is an annotation that was already present in Java 5. However, its semantics changed in Java 6. Therefore, code using @Override, whether it is on a method implementing an interface or not, is source compatible with a Java 5 program. As such, running a JDK 8 with -source 1.5 on such a class will not fail.

Why does it run?

Onto the second parameter: target. Again, this isn't a Maven Compiler concern, but a javac one. While the -source flag enforces source compatibility with an older version, -target enforces binary compatibility with an older version. This flag tells javac to generate byte code that is compatible with an older JVM version. It does not tell javac to check that the compiled code can actually run with the older JVM version. For that, you need to set a bootclasspath, which will cross-compile your code with a specified JDK.

Clearly, @Override on a method implementing an interface cannot run on a Java 5 VM, so javac should bark here. But nope: 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 (which enables an automatic bootclasspath with pre-defined JDK versions) won't detect this: the annotation is missing.

In summary, you can package the sample application above with mvn clean package running on JDK 8, and run it without hitting any issues on a Java 5 JVM. It will print "foo".

How can I make it not compile?

There are two possible solutions.

The first, direct one, is to specify the path to javac through the executable property of the Compiler 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>

This sets the actual version of the JDK the compiler should use with the compilerVersion parameter. This is a simple approach, but note that it only changes the JDK version used for compiling. Maven will still use the JDK 8 installation with which it is launched to generate the Javadoc or run the unit tests, or any step that would require a tool for the JDK installation.

The second, global, approach, is to use a toolchains. These will instruct Maven to use a JDK different than the one used to launch mvn, and every Maven plugins (or any plugin that is toolchains aware) will then use this JDK to perform their operation. Edit your POM file to add the following plugin configuration of the 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>

The missing ingredient is telling those plugins where the configuration for that toolchain is. This is done inside a toolchains.xml file, that is generally inside ~/.m2/toolchains.xml. Starting with Maven 3.3.1, you can define the location to this file using the --global-toolchains parameter, but best to keep it inside the user home. The content would be:

<toolchains>
  <toolchain>
    <type>jdk</type>
    <provides>
      <version>1.5</version>
    </provides>
    <configuration>
      <jdkHome>/usr/lib/jvm/jdk1.5.0_22</jdkHome>
    </configuration>
  </toolchain>
</toolchains>

This declares a toolchain of type jdk providing a JDK 5 with the path to the JDK home. The Maven plugins will now use this JDK. In effect, it will also be the JDK used when compiling the source code.

And if you try to compile again the sample project above with this added configuration... you'll finally have the error:

method does not override a method from its superclass

这篇关于@Override对Java 5代码中实现的接口方法的注释没有给出编译错误的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆