inheritance ejemplo - ¿Por qué no es posible extender las anotaciones en Java?




crear annotation (8)

No entiendo por qué no hay herencia en las anotaciones de Java, al igual que las clases de Java. Creo que sería muy útil.

Por ejemplo: quiero saber si una anotación dada es un validador. Con la herencia, podría navegar reflexivamente a través de superclases para saber si esta anotación extiende una ValidatorAnnotation . De lo contrario, ¿cómo puedo lograr esto?

Entonces, ¿alguien puede darme una razón para esta decisión de diseño?


Answers

En cierto sentido, ya lo tienes con Annotations - meta Annotations. Si anota una anotación con metainformación, esto equivale en muchos aspectos a extender una interfaz adicional. Las anotaciones son interfaces, por lo que el polimorfismo no entra realmente en juego, y como son de naturaleza estática, no puede haber un despacho dinámico en tiempo de ejecución.

En su ejemplo de validador, podría simplemente obtener en la anotación el tipo anotado y ver si tiene una meta-anotación de validador.

El único caso de uso que podría ver que la herencia ayudaría es si quisiera obtener la anotación por supertipo, pero eso agregaría un montón de complejidad, porque un método o tipo dado puede tener dos anotaciones de este tipo, lo que significa que se debería devolver una matriz en lugar de un solo objeto.

Así que creo que la respuesta definitiva es que los casos de uso son esotéricos y complican los casos de uso más estándar, por lo que no vale la pena.


Una cosa en la que podría pensar es en la posibilidad de tener múltiples anotaciones. Así que puedes agregar un validador y una anotación más específica en el mismo lugar. Pero podría estar equivocado :)


Nunca pensé en eso, pero ... parece que tienes razón, no hay ningún problema con la facilidad de herencia de anotaciones (al menos no veo el problema).

Acerca de su ejemplo con anotación 'validador' : puede explotar el enfoque de 'meta-anotación' en ese momento. Es decir, aplica una meta-anotación particular a toda la interfaz de anotación.


Los diseñadores del soporte de anotación de Java hicieron una serie de "simplificaciones" en detrimento de la comunidad de Java.

  1. Ningún subtipo de anotaciones hace que muchas anotaciones complejas sean innecesariamente feas. Uno no puede simplemente tener un atributo dentro de una anotación que pueda contener una de tres cosas. Uno debe tener tres atributos separados, lo que confunde a los desarrolladores y requiere la validación en tiempo de ejecución para garantizar que solo se use uno de los tres.

  2. Sólo una anotación de un tipo dado por sitio. Esto ha llevado a un patrón de anotación de colección completamente innecesario. @Validation y @Validations, @Image y @Images, etc.

El segundo está siendo remediado en Java 8, pero es demasiado tarde. Se han escrito muchos marcos basados ​​en lo que era posible en Java 5 y ahora estas API verrugas están aquí para quedarse por un buen tiempo.


Podría tardar tres años en responder a esta pregunta, pero me pareció interesante porque me encontré en el mismo lugar. Aquí está mi opinión sobre ello. Puedes ver las anotaciones como Enums. Proporcionan un tipo de información unidireccional: la utilizan o la pierden.

Tuve una situación en la que quería simular GET, POST, PUT y DELETE en una aplicación web. Tenía tantas ganas de tener una anotación "super" que se llamaba "HTTP_METHOD". Más tarde me di cuenta de que no importaba. Bueno, tuve que conformarme con el uso de un campo oculto en el formulario HTML para identificar DELETE y PUT (porque POST y GET estaban disponibles de todos modos).

En el lado del servidor, busqué un parámetro de solicitud oculto con el nombre "_método". Si el valor era PUT o DELETE, entonces anuló el método de solicitud HTTP asociado. Dicho esto, no importaba si necesitaba o no extender una anotación para hacer el trabajo. Todas las anotaciones parecían iguales, pero fueron tratadas de manera diferente en el lado del servidor.

Así que en su caso, deje caer la picazón para extender las anotaciones. Trátelos como 'marcadores'. Ellos "representan" cierta información y no necesariamente "manipulan" alguna información.


el mismo problema que tengo No, no puedes. Me "discipliné" a mí mismo para escribir las propiedades en las anotaciones para respetar algunos estándares, por lo que fuera de ellas, puede "oler" qué tipo de anotación es por las propiedades que tiene.


Las anotaciones extensibles agregarían efectivamente la carga de especificar y mantener otro tipo de sistema. Y este sería un sistema de tipo bastante único, por lo que no podría simplemente aplicar un paradigma de tipo OO.

Piense en todos los problemas cuando introduzca polimorfismo y herencia en una anotación (por ejemplo, ¿qué sucede cuando la subnotación cambia las especificaciones de la meta-anotación, como la retención?)

¿Y toda esta complejidad añadida para qué uso?

¿Quieres saber si una anotación dada pertenece a una categoría?

Prueba esto:

@Target(ElementType.ANNOTATION_TYPE)
public @interface Category {
    String category();
}

@Category(category="validator")
public @interface MyFooBarValidator {

}

Como puede ver, puede agrupar y clasificar fácilmente las anotaciones sin molestias utilizando las instalaciones proporcionadas.

Entonces, KISS es la razón para no introducir un sistema de tipo meta-tipo en el lenguaje Java.

[ps edit]

Utilicé el String simplemente como demostración y en vista de una meta anotación abierta. Para su propio proyecto dado, obviamente puede usar una enumeración de tipos de categorías y especificar múltiples categorías ("herencia múltiple") para una anotación dada. Tenga en cuenta que los valores son totalmente falsos y solo para fines de demostración:

@Target(ElementType.ANNOTATION_TYPE)
public @interface Category {
    AnnotationCategory[] category();
}
public enum AnnotationCategory {
    GENERAL,
    SEMANTICS,
    VALIDATION,
    ETC
}

@Category(category={AnnotationCategory.GENERAL, AnnotationCategory.SEMANTICS})
public @interface FooBarAnnotation {

}

Acabo de leer esta pregunta y sus respuestas, y siento que falta una respuesta.

Una forma común de eliminar la predicción de rama que he encontrado que funciona particularmente bien en idiomas administrados es una búsqueda de tabla en lugar de usar una rama (aunque no lo he probado en este caso).

Este enfoque funciona en general si:

  1. Es una mesa pequeña y es probable que se almacene en caché en el procesador
  2. Está ejecutando cosas en un bucle muy ajustado y / o el procesador puede cargar previamente los datos

Antecedentes y por qué

Pfew, entonces, ¿qué diablos se supone que significa eso?

Desde la perspectiva del procesador, su memoria es lenta. Para compensar la diferencia en la velocidad, construyen un par de cachés en su procesador (caché L1 / L2) que compensan eso. Así que imagina que estás haciendo tus buenos cálculos y descubre que necesitas un pedazo de memoria. El procesador obtendrá su operación de "carga" y cargará la memoria en la memoria caché, y luego utilizará la memoria caché para realizar el resto de los cálculos. Debido a que la memoria es relativamente lenta, esta 'carga' ralentizará su programa.

Al igual que la predicción de bifurcaciones, esto se optimizó en los procesadores Pentium: el procesador predice que necesita cargar una parte de los datos e intenta cargarlos en la memoria caché antes de que la operación llegue a la memoria caché. Como ya hemos visto, la predicción de bifurcación a veces es terriblemente errónea: en el peor de los casos, es necesario volver y esperar a que se cargue la memoria, lo que durará una eternidad ( en otras palabras: fallar la predicción de bifurcación es mala, una memoria cargar después de fallar una predicción de rama es simplemente horrible! ).

Afortunadamente para nosotros, si el patrón de acceso a la memoria es predecible, el procesador lo cargará en su caché rápido y todo estará bien.

Lo primero que necesitamos saber es qué es pequeño . Mientras que más pequeño es generalmente mejor, una regla de oro es seguir las tablas de búsqueda que tienen un tamaño de <= 4096 bytes. Como límite superior: si su tabla de búsqueda es más grande que 64K, probablemente vale la pena reconsiderarla.

Construyendo una mesa

Así que hemos descubierto que podemos crear una pequeña mesa. Lo siguiente que debe hacer es obtener una función de búsqueda en su lugar. Las funciones de búsqueda suelen ser funciones pequeñas que utilizan un par de operaciones enteras básicas (y, o, xor, desplazar, agregar, eliminar y quizás multiplicar). Desea que su entrada sea traducida por la función de búsqueda a algún tipo de 'clave única' en su tabla, que simplemente le da la respuesta de todo el trabajo que desea que haga.

En este caso:> = 128 significa que podemos mantener el valor, <128 significa que nos deshacemos de él. La forma más fácil de hacerlo es usando un 'AND': si lo mantenemos, Y lo hacemos con 7FFFFFFF; si queremos deshacernos de él, Y lo hacemos con 0. También notamos que 128 es una potencia de 2, por lo que podemos seguir adelante y hacer una tabla de 32768/128 enteros y llenarla con un cero y una gran cantidad de 7FFFFFFFF.

Idiomas gestionados

Quizás se pregunte por qué esto funciona bien en idiomas administrados. Después de todo, los idiomas administrados comprueban los límites de los arreglos con una rama para asegurarse de que no se arruine ...

Bueno no exactamente... :-)

Ha habido bastante trabajo en la eliminación de esta rama para los idiomas administrados. Por ejemplo:

for (int i=0; i<array.Length; ++i)
   // Use array[i]

En este caso, es obvio para el compilador que la condición de límite nunca será alcanzada. Al menos el compilador JIT de Microsoft (pero espero que Java haga cosas similares) notará esto y eliminará la comprobación por completo. WOW - eso significa que no hay rama. Del mismo modo, se tratará con otros casos obvios.

Si tiene problemas con las búsquedas en los idiomas administrados, la clave es agregar un & 0x[something]FFF a su función de búsqueda para hacer que la verificación de límites sea predecible, y ver cómo va más rápido.

El resultado de este caso.

// Generate data
int arraySize = 32768;
int[] data = new int[arraySize];

Random rnd = new Random(0);
for (int c = 0; c < arraySize; ++c)
    data[c] = rnd.Next(256);

//To keep the spirit of the code in-tact I'll make a separate lookup table
// (I assume we cannot modify 'data' or the number of loops)
int[] lookup = new int[256];

for (int c = 0; c < 256; ++c)
    lookup[c] = (c >= 128) ? c : 0;

// Test
DateTime startTime = System.DateTime.Now;
long sum = 0;

for (int i = 0; i < 100000; ++i)
{
    // Primary loop
    for (int j = 0; j < arraySize; ++j)
    {
        // Here you basically want to use simple operations - so no
        // random branches, but things like &, |, *, -, +, etc. are fine.
        sum += lookup[data[j]];
    }
}

DateTime endTime = System.DateTime.Now;
Console.WriteLine(endTime - startTime);
Console.WriteLine("sum = " + sum);

Console.ReadLine();




java inheritance annotations