为什么 isAnnotationPresent 在 Java 7 和 Java 8 之间的工作方式不同?

Why does isAnnotationPresent work differently between Java 7 and Java 8?

我今天刚发现这一点,当时我的一个单元测试由于从 Java 7 升级到 Java 8 而失败。单元测试调用一个方法,该方法试图在方法上找到注释它在 child class 上注释,但具有不同的 return 类型。

在 Java 7 中,isAnnotationPresent 似乎只能找到真正在代码中声明的注释。在 Java 8 中,isAnnotationPresent 似乎包含在 child classes 中声明的注释。

为了说明这一点,我创建了一个简单的 (??) 测试 class IAPTest(用于 IsAnnotationPresentTest)。

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;

public class IAPTest {
    @Retention(RetentionPolicy.RUNTIME)
    public static @interface Anno {
    }
    public static interface I {
    }
    public static interface IE extends I {
    }
    public static class A {
        protected I method() {
            return null;
        }
    }
    public static class B extends A {
        @Anno
        protected IE method() {
            return null;
        }
    }
    public static void main(String[] args) {
        for (Method method : B.class.getDeclaredMethods()) {
            if (method.getName().equals("method") && I.class.equals(method.getReturnType())) {
                System.out.println(method.isAnnotationPresent(Anno.class));
            }
        }
    }
}

在最新的 Java 7(撰写本文时为 1.7.0_79),此方法打印 "false"。在最新的 Java 8(撰写本文时为 1.8.0_66),此方法打印 "true"。我直觉上希望它打印 "false".

这是为什么?这是否表示 Java 中存在错误或 Java 的工作方式有意更改?

编辑:只是为了显示我用来复制它的确切命令(在 IAPTest.java 与上面的代码块相同的目录中):

C:\test-isannotationpresent>del *.class

C:\test-isannotationpresent>set JAVA_HOME=C:\nma\Toolsets\AJB1\OracleJDK\jdk1.8.0_66

C:\test-isannotationpresent>set PATH=%PATH%;C:\nma\Toolsets\AJB1\OracleJDK\jdk1.8.0_66\bin

C:\test-isannotationpresent>java -version
java version "1.8.0_66"
Java(TM) SE Runtime Environment (build 1.8.0_66-b17)
Java HotSpot(TM) 64-Bit Server VM (build 25.66-b17, mixed mode)

C:\test-isannotationpresent>javac IAPTest.java

C:\test-isannotationpresent>java IAPTest
true

C:\test-isannotationpresent>

我认为这与 java 8 compatibility guide

中提到的更改有关

As of this release, parameter and method annotations are copied to synthetic bridge methods.This fix implies that now for programs like:

@Target(value = {ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME) @interface ParamAnnotation {}  
@Target(value = {ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME) @interface MethodAnnotation {}  
abstract class T<A,B> {
    B m(A a){
        return null;
    }  
}    
class CovariantReturnType extends T<Integer, Integer> {
    @MethodAnnotation
    Integer m(@ParamAnnotation Integer i) {
        return i;
    }

    public class VisibilityChange extends CovariantReturnType {}   
}  

Each generated bridge method will have all the annotations of the method it redirects to. Parameter annotations will also be copied. This change in the behavior may impact some annotations processor or in general any application that use the annotations.

第二种方法 return 是 I 而不是 IE 是生成的合成方法,因为覆盖方法中的 return 类型比在超class。请注意,如果您没有 narrowing return 类型,它不会出现在已声明方法的列表中。所以我认为这不是bug,而是刻意的改动。