6

I am experimenting with static fields in annotations and I am stumbling upon something I do not understand.

I use the following code:

@a
public class myAnnotMinimal {
    public static void main(String[] args) {
        System.out.println(myAnnotMinimal.class.getAnnotations().length);
    }
}

@Retention(RetentionPolicy.RUNTIME)
@interface a {
    Consumer<Integer> f1 = a -> {return;}; // -> 0
    //Consumer<Integer> f2 = new B();  //-> 1
    //Consumer<Integer> f3 = C::eat; //-> 1
    //int f4 = 5; //->1
    //Supplier<Integer> f5 = ()->5; //->1
}

class B implements Consumer<Integer> {
    @Override
    public void accept(Integer t) {
    }
}

class C{
    public static void eat(Integer t){
    }
}

Now, when running this I would expect '1' to be printed, however, the output I get is '0'. When I remove the f1 field and uncomment the other (f2-f5) fields the output is '1'. To me this pretty much looks like a bug. Is there something I am missing? I use jdk1.8.0_66 on linux.

Since this looks like a bug in the JDK I filed a bug report. By now the bug report has been accepted. See http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8147585 .

miselico
  • 355
  • 3
  • 9
  • This looks to a bug in sun.reflect.annotation.AnnotationType where they should ignore the synthetic methods. – Mrinal Jan 12 '16 at 19:43
  • @Mrinal K. Samanta: or to put it the other way round, the annotation introspection should only deal with `public abstract` methods. But anyway, putting such a field into an annotation type, doesn’t look like a recommended coding style anyway. – Holger Jan 12 '16 at 20:10
  • Whoah, took me 5 minutes to understand this was actually declaring a `static final` field! I didn't think this would have been allowed at all. This looks like a bug indeed but… what's the point? – Didier L Jan 12 '16 at 22:49
  • I agree that this is a weird coding style. The only somewhat reasonable use I have seen is to provide helper methods for the annotation (in that case a validation method). I just noticed that this possibility existed and started experimenting. – miselico Jan 13 '16 at 09:55

1 Answers1

8

This is a bug in the JRE’s annotation processing or, more precisely, its code hasn’t updated properly to accommodate the new language features, though it’s understandable that such usage hasn’t been considered.

Annotations are not supposed to provide utility methods and the ability to declare constants, i.e. fields that are implicitly public static final, should not be abused to add functions which carry code.

The reason for the bug is that certain Java language constructs produce synthetic methods behind the scenes. Field initializers may add code to a static method <clinit> at bytecode level, which is known and ignored by Reflection, thus creates no problems. That’s why Consumer<Integer> f2 = new B(); works (as it would do before Java 8), the creation happens inside this class initialization method. Note that int f4 = 5; creates a compile-time constant which does not need initialization code at all.

However, lambda expressions are compiled into synthetic methods which apparently are not ignored by the runtime Annotation implementation, despite being private, but checked for conformance with the standard annotation methods.

That’s why Supplier<Integer> f5 = ()->5; creates no problems, the synthetic method incidentally conforms to the annotation method pattern, as it has no parameters and returns a value. In contrast, Consumer<Integer> f1 = i->System.out.println(i); is compiled into a synthetic method having one parameter and void return type. This seems to cause the Annotation processing facility to reject that annotation as invalid (without reporting it).

In contrast, most method references do not need a synthetic helper method as they direct to the target method, thus Consumer<Integer> f3 = C::eat; creates no problems. You can verify this pattern by changing the declaration of f1 to Consumer<Integer> f1 = System.out::println;, while being semantically equivalent, the problem disappears, as now there’s no offending synthetic method in the annotation class file.

While this is indeed a bug, as, when when the Java language accepts lambda expression inside annotation fields, the JRE implementation should catch up to handle that scenario, I strongly discourage you from adding code to annotation types that way. Saving a single utility class is not worth that code smell. Also keep in mind that this creates additional runtime overhead compared to ordinary utility methods.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • Why do you say that: "this creates additional runtime overhead compared to ordinary utility methods."? – miselico Jan 13 '16 at 16:38
  • Well, you have to go through the lambda expression bootstrapping during the class initialization. Then, for each invocation, instead of invoking a, e.g. `static`, utility method directly, you are invoking an `interface` method whose runtime-generated implementation will eventually invoke the actual utility method. Of course, the HotSpot optimizer is capable of eliminating most of the overhead, but it’s still a difference to an ordinary method which hasn’t that overhead in the first place. Note that I intentionally mentioned that overhead in the *last* sentence. It’s not that important. – Holger Jan 13 '16 at 16:48