Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic types in annotations

Consider the following code:

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

public class AnnotationTest {

    @GenericAnnotation<String>(foo = "Test")
    public class Bar1 {

    }

    @ObjectAnnotation(foo = "Test")
    public class Bar2 {

    }

    @WorkingAnnotation(foo = "Test")
    public class Bar3 {

    }

    @Retention(RetentionPolicy.RUNTIME)
    public @interface GenericAnnotation<T> {

        public T foo();

    }

    @Retention(RetentionPolicy.RUNTIME)
    public @interface ObjectAnnotation {

        public Object foo();

    }

    @Retention(RetentionPolicy.RUNTIME)
    public @interface WorkingAnnotation {

        public String foo();

    }

}

Bar1 won't compile at all. Huge mess of errors.

Bar2 will compile fine, but the ObjectAnnotation annotation won't.

Bar3 will compile fine, but doesn't allow generic types.


If - for example - I am trying to set a default value in case a certain field can't be loaded. This class might be an Integer, String, Boolean[], really any of the possible types. This means a whole mess of annotations for handling every possibly case.


Is there a proper way to handle generic types in an annotation? If not, is there a clear reason why?

like image 206
Obicere Avatar asked Sep 29 '22 17:09

Obicere


1 Answers

The compiler's error messages are confusing purely because it is not laid out to accept such a syntax.

A similar question was already posted here.

Case 1

The JLS, Section 9.6 states the general syntax of an annotation declaration as follows:

AnnotationTypeDeclaration:

{InterfaceModifier} @ interface Identifier AnnotationTypeBody

The token Identifier, in the terms of the JLS, describes the name of the type; be it class, enum, interface or annotation.

An annotation can not be declared with generic types, as these are referred to by the token TypeParameters, which is not included here.

As to why, this leads to

Case 2

Looking into the next item, Section 9.6.1, we spot the restriction of types an annotation can take:

The return type of a method declared in an annotation type must be one of the following, or a compile-time error occurs:

  • A primitive type
  • String
  • Class or an invocation of Class (§4.5)
  • An enum type
  • An annotation type
  • An array type whose component type is one of the preceding types (§10.1).

(some special cases are addressed further below, but are irrelevant to this problem)

These restrictions are what's causing the dejection of your second annotation. It is simply not designed to hold all types of Objects. It cannot even hold the boxed type of a primitive!

Now, back to Case 1 for a bit: Why are generic Annotations a wrong thing in this grammar? Even before any type erasure occurs, your GenericAnnotation can basically be a holder for every type of Object. It is, in the definition of its foo property, exactly the same as your ObjectAnnotation.

Now, the question is, why is that limitation in place? By limiting the values an annotation might contain, you get two advantages: First, all the values are to be compile-time constants. There is no way to get a runtime-dependent value either into or out of an annotation without heavy use of reflection.

This immediately brings one to the second advantage: Nobody will even get the temptation to put impure (side-effecting) code inside annotations which may be loaded at any random point in the applications lifetime. If you could introduce any type of object, their constructors could do any kind of side-effect at a possibly undetectable time, increasing the complexity of debugging if this technique were used.

Or at least, that's what seems the most logical to me, as there is no official word on this from either Sun or their successors that I could find.

Possible Workaround: None

There is, sadly, no simple, expressive and easy workaround to this. As you cannot place processes into @Annotation-uses, you can not even do something fancy as serializing the object into a byte array using ObjectOutputStream and ByteArrayOutputStream.

like image 110
Adowrath Avatar answered Oct 07 '22 20:10

Adowrath