Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I just discovered this today when one of my unit tests failed because of upgrading from Java 7 to Java 8. The unit test calls a method which tries to find an annotation on a method which is annotated on a child class but with a different return type.

In Java 7, isAnnotationPresent seems to only find annotations if they were really declared in code. In Java 8, isAnnotationPresent seems to include annotations that were declared in child classes.

To illustrate this I created a simple (??) test class IAPTest (for 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));
            }
        }
    }
}

On the latest Java 7 (1.7.0_79 at time of writing), this method prints "false". On the latest Java 8 (1.8.0_66 at time of writing), this method prints "true". I would intuitively expect it to print "false".

Why is this? Does this indicate a bug in Java or an intended change in how Java works?

EDIT: Just to show the exact commands I used to replicate this (in a directory with IAPTest.java identical to the code block above):

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>
like image 617
Adam Burley Avatar asked Nov 05 '15 16:11

Adam Burley


People also ask

Why Java 8 is faster than Java 7?

The most anticipated feature — Lambda Expressions, was introduced and made possible from Java 8. The use of this Lambda Expressions made Java closer to functional programming which it was not used to be. Java 8 also includes a faster and improved JVM which makes it possible to run programs more efficiently.

What is Annotated element?

public interface AnnotatedElement. Represents an annotated element of the program currently running in this VM. This interface allows annotations to be read reflectively. All annotations returned by methods in this interface are immutable and serializable.


1 Answers

I believe this is related to a change mentioned in the 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.

The second method that returns an I instead of an IE is a synthetic method generated because you have a narrower return type in the overridden method than in the super class. Note that it's not present in the list of declared methods if you don't have a narrowing return type. So I think this is not a bug, but a deliberate change.

like image 90
whaleberg Avatar answered Sep 18 '22 14:09

whaleberg