Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

java8 - annotating compareTo <T> of Comparable<T> adds annotations to compareTo(Object o)

I have an annotation

package javaannotationtest;

import java.lang.annotation.*;

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

This is applied to compareTo in following class

package javaannotationtest;

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

The class gives different result with java-7 and java-8 compiled code.

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

Note that compareTo(Object) does not have the annotation.

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 has annotation added to the compareTo(java.lang.Object) method

Here is output from javap for the version compiled with Java 8 (probably not relevant, it shows the annotation added to both methods)

  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()
}

Could someone explain relevant changes in Java 8? (Will offer Bounty when is becomes eligible).

like image 431
Jayan Avatar asked Apr 17 '14 08:04

Jayan


1 Answers

This change is described in the issue JDK-6695379 - Copy method annotations and parameter annotations to synthetic bridge methods. There doesn't seem to be much discussion about this feature but the request and justification do make sense to me.

A DESCRIPTION OF THE REQUEST : When a class extends a generic classes or implements a generic interface, synthetic method may be generated to bridge between the method taking specific parameters/return and the one of the super-class/interface which is defined with Objects, because of erasure. A bridge method redirects the call to the actual method, according to Java Language Specification. However the bridge method lacks the annotations defined for the original method and its parameters.

JUSTIFICATION : The problem arises when trying to retrieve annotations of such a method at run-time. Since it's impossible to find out reliably what classes substitute the generic parameters, we don't know what parameter to send to getMethod(...) in order to receive the correct method back. When sending Object.class (while generic parameter is a different class) getMethod will return the bridge method, which won't have the information about the original method annotations.

It is also documented in the JDK 8 compatibility guide:

Area: Tools / javac

Synopsis
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.

Nature of Incompatibility
behavioral

RFE
6695379

like image 76
kapex Avatar answered Sep 18 '22 12:09

kapex