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

Kid*_*rla 6 java reflection annotations java-7 java-8

我刚刚发现这个,因为我的一个单元测试由于从Java 7升级到Java 8而失败.单元测试调用一个方法,该方法试图在一个在子类上注释但具有不同返回类型的方法上查找注释.

在Java 7中,isAnnotationPresent似乎只能找到注释,如果它们真的是在代码中声明的.在Java 8中,isAnnotationPresent似乎包含在子类中声明的注释.

为了说明这一点,我创建了一个简单的(??)测试类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));
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

为什么是这样?这是否表明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>
Run Code Online (Sandbox Code Playgroud)

wha*_*erg 10

我相信这与java 8兼容性指南中提到的更改有关

在此版本中,参数和方法注释被复制到合成桥接方法.此修复意味着现在对于以下程序:

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

每个生成的桥接方法都将具有重定向到的方法的所有注释.参数注释也将被复制.行为的这种改变可能会影响某些注释处理器或通常任何使用注释的应用程序.

返回an I而不是IEa 的第二种方法是生成的合成方法,因为重写方法中的返回类型比超类中的返回类型更窄.请注意,如果您没有缩小返回类型,则它不会出现在声明的方法列表中.所以我认为这不是一个错误,而是一个故意的改变.