Java8 - аннотирование compareTo <T> of Comparable <T> добавляет аннотации к compareTo (Object o)

У меня есть аннотация

package javaannotationtest;

import java.lang.annotation.*;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomAnnotation {
}

Это применяется для сравнения в следующем классе

package javaannotationtest;

public class Customer implements Comparable<Customer>{
    @Override
    @CustomAnnotation
    public int compareTo(Customer o) {
        return 0;
    }
}

Класс дает другой результат с скомпилированным кодом java-7 и java-8.

Java 7

1.7.0_45 -> public int javaannotationtest.Customer.compareTo(javaannotationtest.Customer)
 has  annotation of type javaannotationtest.CustomAnnotation
1.7.0_45 -> public int javaannotationtest.Customer.compareTo(java.lang.Object)
 has no annotation of type javaannotationtest.CustomAnnotation

Обратите внимание, что compareTo (Object) не имеет аннотации.

Java 8

1.8.0 -> public int javaannotationtest.Customer.compareTo(javaannotationtest.Customer)
 has  annotation of type javaannotationtest.CustomAnnotation
1.8.0 -> public int javaannotationtest.Customer.compareTo(java.lang.Object)
 has  annotation of type javaannotationtest.CustomAnnotation

Java 8 добавляет аннотацию к методу compareTo(java.lang.Object)

Здесь выводится из javap для версии, скомпилированной с Java 8 (вероятно, не актуально, она показывает аннотацию, добавленную к обоим методам)

  Classfile /C:/code/java8annoation/out/production/java8annoation/javaannotationtest/Customer.class
  Last modified 17 Apr, 2014; size 719 bytes
  MD5 checksum 678e0371f5f9ed5666b513c940f365a7
  Compiled from "Customer.java"
public class javaannotationtest.Customer extends java.lang.Object implements java.lang.Comparable<javaannotationtest.Customer>
  Signature: #20                          // Ljava/lang/Object;Ljava/lang/Comparable<Ljavaannotationtest/Customer;>;
  SourceFile: "Customer.java"
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#23         //  java/lang/Object."<init>":()V
   #2 = Class              #24            //  javaannotationtest/Customer
   #3 = Methodref          #2.#25         //  javaannotationtest/Customer.compareTo:(Ljavaannotationtest/Customer;)I
   #4 = Class              #26            //  java/lang/Object
   #5 = Class              #27            //  java/lang/Comparable
   #6 = Utf8               <init>
   #7 = Utf8               ()V
   #8 = Utf8               Code
   #9 = Utf8               LineNumberTable
  #10 = Utf8               LocalVariableTable
  #11 = Utf8               this
  #12 = Utf8               Ljavaannotationtest/Customer;
  #13 = Utf8               compareTo
  #14 = Utf8               (Ljavaannotationtest/Customer;)I
  #15 = Utf8               o
  #16 = Utf8               RuntimeVisibleAnnotations
  #17 = Utf8               Ljavaannotationtest/CustomAnnotation;
  #18 = Utf8               (Ljava/lang/Object;)I
  #19 = Utf8               Signature
  #20 = Utf8               Ljava/lang/Object;Ljava/lang/Comparable<Ljavaannotationtest/Customer;>;
  #21 = Utf8               SourceFile
  #22 = Utf8               Customer.java
  #23 = NameAndType        #6:#7          //  "<init>":()V
  #24 = Utf8               javaannotationtest/Customer
  #25 = NameAndType        #13:#14        //  compareTo:(Ljavaannotationtest/Customer;)I
  #26 = Utf8               java/lang/Object
  #27 = Utf8               java/lang/Comparable
{
  public javaannotationtest.Customer();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0       
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return        
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Ljavaannotationtest/Customer;

  public int compareTo(javaannotationtest.Customer);
    descriptor: (Ljavaannotationtest/Customer;)I
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=2, args_size=2
         0: iconst_0      
         1: ireturn       
      LineNumberTable:
        line 7: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       2     0  this   Ljavaannotationtest/Customer;
            0       2     1     o   Ljavaannotationtest/Customer;
    RuntimeVisibleAnnotations:
      0: #17()

  public int compareTo(java.lang.Object);
    descriptor: (Ljava/lang/Object;)I
    flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0       
         1: aload_1       
         2: checkcast     #2                  // class javaannotationtest/Customer
         5: invokevirtual #3                  // Method compareTo:(Ljavaannotationtest/Customer;)I
         8: ireturn       
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  this   Ljavaannotationtest/Customer;
    RuntimeVisibleAnnotations:
      0: #17()
}

Может кто-нибудь объяснить соответствующие изменения в Java 8? (Будет предлагать Bounty, когда он станет подходящим).

Ответ 1

Это изменение описано в выпуске JDK-6695379 - Копировать аннотации методов и аннотации параметров к синтетическим моделям. По-видимому, не так много обсуждений этой функции, но запрос и обоснование имеют смысл для меня.

A ОПИСАНИЕ ЗАЯВКИ: Когда класс расширяет общие классы или реализует общий интерфейс, синтетический метод может быть сгенерирован для соединения между методом, принимающим конкретные параметры /return, и одним из суперкласса/интерфейса, который определен объектами из-за стирания. Метод моста перенаправляет вызов на фактический метод, в соответствии с Спецификацией языка Java. Однако в методе моста отсутствуют аннотации, определенные для исходного метода и его параметров.

ОБОСНОВАНИЕ: Проблема возникает при попытке получить аннотации такого метода во время выполнения. Поскольку невозможно достоверно узнать, какие классы заменяют общие параметры, мы не знаем, какой параметр следует отправить getMethod (...), чтобы получить правильный метод обратно. При отправке Object.class(в то время как общий параметр является другим классом) getMethod вернет метод bridge, который не будет иметь информацию об аннотациях исходного метода.

Это также описано в Руководстве по совместимости JDK 8:

Область: Инструменты /javac

Сводка
Начиная с этой версии, аннотации параметров и методов копируются на синтетические мостовые методы. Это исправление подразумевает, что теперь для таких программ, как:

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

}

Каждый созданный метод моста будет иметь все аннотации метода, к которому он перенаправляет. Также будут скопированы аннотации параметров. Это изменение поведения может повлиять на какой-либо обработчик аннотаций или вообще любое приложение, использующее аннотации.

Характер несовместимости
поведенческий

RFE
6695379

Ответ 2

@kapep: очень хороший ответ. Я хотел бы добавить дополнительную информацию об этой проблеме. К мостовому методу копируются не только аннотации. Имена параметров также скопированы. Если вы скомпилируете пример с параметром -parameters compiler, вы получите этот выход javap:

public int compareTo(Customer);
descriptor: (LCustomer;)I
flags: ACC_PUBLIC
Code:
  stack=1, locals=2, args_size=2
     0: iconst_0      
     1: ireturn       
  LineNumberTable:
    line 11: 0
MethodParameters:
  Name                           Flags
  o                              
RuntimeVisibleAnnotations:
  0: #15()

public int compareTo(java.lang.Object);
descriptor: (Ljava/lang/Object;)I
flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
Code:
  stack=2, locals=2, args_size=2
     0: aload_0       
     1: aload_1       
     2: checkcast     #2                  // class Customer
     5: invokevirtual #3                  // Method compareTo:(LCustomer;)I
     8: ireturn       
  LineNumberTable:
    line 7: 0
MethodParameters:
  Name                           Flags
  o                              synthetic  <-- see the name copied to the bridge method
RuntimeVisibleAnnotations:
  0: #15()