Error al establecer un valor nulo predeterminado para el campo de una anotación

79

¿Por qué recibo un error "El valor del atributo debe ser constante"? ¿No es nula constante ???

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SomeInterface {
    Class<? extends Foo> bar() default null;// this doesn't compile
}
destripador234
fuente
¿Por qué alguien necesitaría esto (para compilar o solucionar)? La misma "funcionalidad" ya la otorga Class<? extends Foo> bar();.
xerx593
3
Porque como ya no hay un valor predeterminado, el campo se vuelve obligatorio en el uso de anotaciones
Donatello

Respuestas:

64

No sé por qué, pero el JLS es muy claro:

 Discussion

 Note that null is not a legal element value for any element type. 

Y la definición de un elemento predeterminado es:

     DefaultValue:
         default ElementValue

Desafortunadamente, sigo encontrando que las nuevas funciones del lenguaje (Enums y ahora Anotaciones) tienen mensajes de error del compilador muy inútiles cuando no cumples con las especificaciones del lenguaje.

EDITAR: Un poco de búsqueda en Google encontró lo siguiente en el JSR-308, donde argumentan a favor de permitir nulos en esta situación:

Observamos algunas posibles objeciones a la propuesta.

La propuesta no hace posible nada que no fuera posible antes.

El valor especial definido por el programador proporciona una mejor documentación que nulo, lo que podría significar "ninguno", "no inicializado", nulo en sí, etc.

La propuesta es más propensa a errores. Es mucho más fácil olvidar la comprobación de un valor nulo que olvidar la comprobación de un valor explícito.

La propuesta puede hacer que el modismo estándar sea más detallado. Actualmente, solo los usuarios de una anotación necesitan verificar sus valores especiales. Con la propuesta, muchas herramientas que procesan anotaciones tendrán que verificar si el valor de un campo es nulo para que no arrojen una excepción de puntero nulo.

Creo que solo los dos últimos puntos son relevantes para "por qué no hacerlo en primer lugar". El último punto ciertamente trae a colación un buen punto: un procesador de anotaciones nunca tiene que preocuparse de que obtendrán un valor nulo en un valor de anotación. Tiendo a ver eso como más trabajo de los procesadores de anotaciones y otro código de marco similar tener que hacer ese tipo de verificación para que el código de los desarrolladores sea más claro en lugar de al revés, pero ciertamente haría difícil justificar el cambio.

Yishai
fuente
67

Prueba esto

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SomeInterface {
    Class bar() default void.class;
}

No requiere una nueva clase y ya es una palabra clave en Java que no significa nada.

Logan Murphy
fuente
8
Me gusta. El único problema, en el contexto de la pregunta original, es que esta respuesta no admite parámetros de tipo: Class<? extends Foo> bar() default void.null no se compila.
Kariem
3
La clase void.class no se puede aplicar de manera general: public @interface NoNullDefault {String value () default void.class; }
wjohnson
Para Groovy funciona bien, incluso en los casos mencionados en los comentarios
Vampire
LOL: eso no significa nada versus eso que significa "nada"
Andreas
55

Parecería que esto es ilegal, aunque el JLS es muy confuso al respecto.

Arruiné mi memoria para intentar pensar en una anotación existente que tuviera un atributo de clase para una anotación, y recordé esta de la API de JAXB:

@Retention(RUNTIME) @Target({PACKAGE,FIELD,METHOD,TYPE,PARAMETER})        
public @interface XmlJavaTypeAdapter {
    Class type() default DEFAULT.class;

    static final class DEFAULT {}    
}

Puede ver cómo han tenido que definir una clase estática ficticia para mantener el equivalente a un valor nulo.

Desagradable.

skaffman
fuente
3
Sí, hacemos algo similar (solo usamos Foo.class como predeterminado). Apesta.
ripper234
7
Y en el caso de los campos String, hay que recurrir a cadenas mágicas . Aparentemente, los antipatrones conocidos son mejores que las características del lenguaje bien entendidas ahora.
Tim Yates
3
@TimYates Me mata ver a la gente definir su propio valor centinela "sin valor" cuando el valor nulo está disponible y se entiende universalmente. ¡¿Y ahora eso lo exige el propio idioma ?! Afortunadamente, puede definir sus "cadenas mágicas" como constantes públicas. Todavía no es ideal, pero al menos el código que busca su anotación puede hacer referencia a la constante en lugar de usar literales en todas partes.
spaaarky21
11

Parece que hay otra forma de hacerlo.

Esto tampoco me gusta, pero podría funcionar.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SomeInterface {
    Class<? extends Foo>[] bar() default {};
}

Entonces, básicamente, creas una matriz vacía en su lugar. Esto parece permitir un valor predeterminado.

Shervin Asgari
fuente
3
Class<? extends Foo> bar() default null;// this doesn't compile

Como se mencionó, la especificación del lenguaje Java no permite valores nulos en los valores predeterminados de las anotaciones.

Lo que suelo hacer es definir DEFAULT_VALUEconstantes en la parte superior de la definición de la anotación. Algo como:

public @interface MyAnnotation {
    public static final String DEFAULT_PREFIX = "__class-name-here__ default";
    ...
    public String prefix() default DEFAULT_PREFIX;
}

Luego en mi código hago algo como:

if (myAnnotation.prefix().equals(MyAnnotation.DEFAULT_PREFIX)) { ... }

En su caso con una clase, simplemente definiría una clase de marcador. Desafortunadamente, no puede tener una constante para esto, por lo que debe hacer algo como:

public @interface MyAnnotation {
    public static Class<? extends Foo> DEFAULT_BAR = DefaultBar.class;
    ...
    Class<? extends Foo> bar() default DEFAULT_BAR;
}

Su DefaultFooclase sería solo una implementación vacía para que el código pueda hacer:

if (myAnnotation.bar() == MyAnnotation.DEFAULT_BAR) { ... }

Espero que esto ayude.

gris
fuente
Por cierto, intenté esto con una cadena y no funciona. No obtienes el mismo objeto String.
Doradus
¿ Usaste .equals()y no ==@Doradus? ¿Obtuvo un valor diferente o un objeto diferente?
Gray
Objeto diferente. Yo usé ==.
Doradus
Bueno, la clase es un singleton @Doradus. Hubiera esperado que String también fuera un singleton, pero supongo que no lo es y necesitarás usarlo .equals(). Sorprendente.
Gray
1

Ese mensaje de error es confuso, pero no, el nullliteral no es una expresión constante, como se define en la especificación del lenguaje Java, aquí .

Además, la especificación del lenguaje Java dice

Es un error en tiempo de compilación si el tipo de elemento no es acorde (§9.7) con el valor predeterminado especificado.

Y para explicar lo que eso significa

Es un error en tiempo de compilación si el tipo de elemento no es acorde con el valor del elemento. Un tipo de elemento T es proporcional a un valor de elemento V si y solo si se cumple una de las siguientes condiciones:

  • Tes un tipo de matriz E[]y:
    • [...]
  • Tno es un tipo de matriz y el tipo de Vasignación es compatible (§5.2) con T, y :
    • Si Tes un tipo primitivo o String, entonces Ves una expresión constante (§15.28).
    • Si Tes Classo una invocación de Class(§4.5), entonces Ves un literal de clase (§15.8.2).
    • Si Tes un tipo de enumeración (§8.9), entonces Ves una constante de enumeración (§8.9.1).
    • V no es nulo .

Entonces, aquí, su tipo de elemento Class<? extends Foo>,, no es acorde con el valor predeterminado, porque ese valor es null.

Sotirios Delimanolis
fuente
Su solución no es adecuada para copiar y pegar ;-) A algunos no les gusta pensar cuando leen
Matthias M
1
En mi humilde opinión, sí, lo hiciste (pero no fui yo). Su expresión se lee como "cuando (...) o (V no es nulo)", lo que parece que cualquier valor no nulo sería correcto. La formulación de JLS es una verdadera bestia, pero hay un exterior ory un interior and, que no se pueden dejar de lado.
maaartinus
@maaartinus No había visto su comentario de hace tanto tiempo, actualicé mi respuesta para agregar la cita completa.
Sotirios Delimanolis
1

Como han dicho otros, hay una regla que dice que nulo no es un valor predeterminado válido en Java.

Si tiene un número limitado de valores posibles que puede tener el campo , entonces una opción para solucionar esto es hacer que el tipo de campo sea una enumeración personalizada que a su vez contenga una asignación a nulo. Por ejemplo, imagina que tengo la siguiente anotación para la que quiero un valor nulo predeterminado:

public @interface MyAnnotation {
   Class<?> value() default null;
}

Como hemos establecido, esto no está permitido. Lo que podemos hacer en cambio es definir una enumeración:

public enum MyClassEnum {
    NULL(null),
    MY_CLASS1(MyClass1.class),
    MY_CLASS2(MyClass2.class),
    ;

    public final Class<?> myClass;

    MyClassEnum(Class<?> aClass) {
        myClass = aClass;
    }
}

y cambie la anotación como tal:

public @interface MyAnnotation {
  MyClassEnum value() default MyClassEnum.NULL;
}

La principal desventaja de esto (además de tener que enumerar sus posibles valores) es que ahora ha envuelto su valor en una enumeración, por lo que deberá invocar la propiedad / captador en la enumeración para obtener el valor.

AMTerp
fuente