2015-11-05 35 views
6

L'ho appena scoperto quando uno dei miei test di unità è fallito a causa dell'aggiornamento da Java 7 a Java 8. Il test dell'unità chiama un metodo che cerca di trovare un'annotazione su un metodo annotato su una classe figlia ma con un diverso tipo di ritorno.Perché isAnnotationPresent funziona in modo diverso tra Java 7 e Java 8?

In Java 7, isAnnotationPresent sembra trovare solo annotazioni se sono state realmente dichiarate nel codice. In Java 8, isAnnotationPresent sembra includere le annotazioni dichiarate nelle classi figlie.

Per illustrare ciò ho creato una semplice (??) classe di test IAPTest (per 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)); 
      } 
     } 
    } 
} 

Sul più recente Java 7 (1.7.0_79 al momento della scrittura), questo metodo stampa "false". Sull'ultimo Java 8 (1.8.0_66 al momento della scrittura), questo metodo stampa "true". Prevedo intuitivamente che stampi "false".

Perché è questo? Questo indica un bug in Java o un cambiamento previsto nel funzionamento di Java?

EDIT: Giusto per mostrare i comandi esatti che ho usato per replicare questo (in una directory con IAPTest.java identico al blocco di codice di cui sopra):

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> 
+0

Hmm, questo stampa 'false' per me: Eclipse Mars 4.5.1, JDK 1.8.0_51. – Tunaki

+0

Perché pensi che le annotazioni "siano state dichiarate in classi di bambini"? Hai annotato il metodo in 'B' e stai cercando i * metodi dichiarati * di' B' e nient'altro. Non ci sono classi di bambini coinvolti. – Holger

+0

@Holger quando cerco i metodi dichiarati di 'B', trovo due metodi. Un metodo rappresenta il metodo sulla classe figlio e uno rappresenta il metodo sulla classe genitore. Puoi dirlo dai tipi di ritorno. 'B.class.getDeclaredMethods(). Length == 2' su Java 7 e 8. Perché sto verificando il tipo restituito per garantire che il metodo della classe genitore sia quello a cui si fa riferimento (il tipo restituito è' I' anziché 'IE'), il metodo in' A' non ha l'annotazione, ma ha solo l'annotazione nella classe figlio 'B'. – Kidburla

risposta

10

Credo che questo è legato ad un cambiamento menzionato nel java 8 compatibility guide

in questa versione, il parametro e il metodo annotazioni vengono copiati methods.This ponte sintetici fissare implica che ora programmi come:

@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 {} 
} 

Ogni metodo di bridge generato avrà tutte le annotazioni del metodo a cui reindirizza. Verranno copiate anche le annotazioni dei parametri. Questa modifica del comportamento potrebbe influire sul processore di annotazioni o su in generale su qualsiasi applicazione che utilizza le annotazioni.

Il secondo metodo che restituisce un I invece di un IE è un metodo sintetico generato perché si ha un tipo di ritorno più stretta nel metodo override che nella classe eccellente. Si noti che non è presente nell'elenco dei metodi dichiarati se non si dispone di un tipo di restrizione a restringimento. Quindi penso che questo non sia un bug, ma un cambiamento intenzionale.

+1

Ottima risposta! Non solo mi hai fornito il ragionamento e la citazione della documentazione, ma anche il modo di correggere il mio test unitario per lavorare su Java 8 - Ho solo bisogno di usare 'isSynthetic'. Grazie! – Kidburla