martes, 4 de febrero de 2014

Anotaciones en Java - Parte 2

Anteriormente...

Tipos Aceptados


No todos los tipos pueden ser aceptados en una Anotación. Los tipos utilizables son:
  • Tipos Primitivos: int, float, short, double, long, boolean, byte, char.
  • String
  • Class
  • Enum: como en el ejemplo de la parte 1.
  • Annotation: anotaciones.
  • Array: un array de cualquiera de los anteriores (String[]).

Uso de Anotaciones, Reflexión / Reflection


El uso mas simple de una anotación es el de ayuda en el código, es decir pueden crearse anotaciones para informar algo al programador (por ejemplo @Deprecated), con el fin de organizar u ordenar nuestro código. Pero el uso de las anotaciones va mas allá, es por algo que los frameworks actuales hacen uso de estas para su funcionamiento.
Por ejemplo Hibernate y JPA (herramientas ORM) dan la posibilidad de definir el modelo de datos, es decir las tablas de una base de datos y sus relaciones utilizando clases y mediante anotaciones se explican características propias de una base de datos.
Me explico mejor con un ejemplo. Una clase que explicaría una tabla de una base de datos podría ser la siguiente:

public class Persona{
    private String idNum;
    private String nombre;
    private String direccion;
    private String telefono;
}
Si bien esta clase nos brinda información importante como que existe una tabla Persona y tiene los campos idNum, nombre, direccion y telefono, esta información no es suficiente, como respondo a las siguientes preguntas
  • ¿Cual es el PrimaryKey de esta Tabla?
  • ¿Cuales son los campos que pueden ser nulos?
  • ¿Cual es la longitud máxima de cada campo?
  • ¿La tabla en la base de datos se llama también "Persona"?
  • ¿Algún campo es ForeignKey de otra tabla?
  • etc
Aqui es donde juegan un papel importante las anotaciones porque nos permite agregar información adicional a los campos, metodos, clase, parametros, etc., sin afectar el codigo en si. La clase equivalente con anotaciones podría ser:

...
@Entity
@Table (name = "persona")
public class Persona implements java.io.Serializable {
@Id
@Column(name = "prsId", length = 15, nullable = false)
private String idNum;
@Column(name = "nombre", length = 200, nullable = false)
private String nombre;
@Column(name = "direccion", length = 200, nullable = false)
private String direccion;
@Column(name = "telefono", length = 15, nullable = false)
private String telefono;
        ...
}
...

El anterior es un fragmento de código que JPA o Hibernate utiliza para explicar al framework como se estructura la tabla, una explicación breve es: @Id indica el campo que es el PrimaryKey de la tabla, @Column indica que es una campo de la tabla y sus atributos indican el nombre en la base de datos(name) longitud máxima (length) si puede ser nulo (nullable).
En si el funcionamiento del framework no es el objetivo de este artículo, sino el de presentar una idea del uso que pueden tener las anotaciones.

Reflexión/Reflection


La Reflexión hace referencia a la lectura de las anotaciones. Una de los requerimientos para poder leer las anotaciones en tiempo de ejecución es que esta se de tipo @Retention(RetentionPolicy.RUNTIME).

La lectura de las anotaciones se la realiza utilizando las herramientas de java.lang.reflect (es por ello el nombre Reflection), En el siguiente ejemplo se mostraran los métodos y campos de la clase java.util.Date que tienen la anotación @Deprecated:

...
        java.lang.reflect.Method[] ms = java.util.Date.class.getDeclaredMethods();
        for (java.lang.reflect.Method m : ms) {
            Deprecated an = m.getAnnotation(Deprecated.class);
            if (an != null) {
                System.out.println("El método " + m.getName() + " tiene @Deprecated");
            }
        }
        java.lang.reflect.Field[] fs = java.util.Date.class.getDeclaredFields();
        for (java.lang.reflect.Field f : fs) {
            Deprecated an = f.getAnnotation(Deprecated.class);
            if (an != null) {
                System.out.println(f.getName()+" tiene @Deprecated");
                //an.atributo(); //para obtener el atributo de la anotación
            }
        }
...

El resultado será

El método parse tiene @Deprecated
El método getDate tiene @Deprecated
El método UTC tiene @Deprecated
El método getDay tiene @Deprecated
El método getHours tiene @Deprecated
El método getMinutes tiene @Deprecated
El método getMonth tiene @Deprecated
El método getSeconds tiene @Deprecated
El método getYear tiene @Deprecated
El método setDate tiene @Deprecated
El método setHours tiene @Deprecated
El método setMinutes tiene @Deprecated
El método setMonth tiene @Deprecated
El método setSeconds tiene @Deprecated
El método setYear tiene @Deprecated
El método toGMTString tiene @Deprecated
El método getTimezoneOffset tiene @Deprecated
El método toLocaleString tiene @Deprecated

Entonces todos estos metodos estan obsoletos en la clase java.util.Date.
El algoritmo anterior es una idea de lo que se puede hacer con el paquete reflect, para una explicación mas avanzada revisa el tutorial Reflection - java.lang.reflect.*

Para obtener el atributo de una anotación y un ejemplo mas completo revisa la parte 3.

Consideraciones


Los valores de los atributos de una anotación tiene que ser constantes al momento de compilar es decir NO puede utilizarse:

@MyAnotacion(atributo = "x" + obj.metodo())

Una anotación puede tener varios @Target:

@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface myAnotacion

Para definir default de un Array es:

public String[] atributo() default {};
...
public int[] atributo() default {};

Las anotaciones pueden ser tan complejas como:

@AnotacionPadre({

    @AnotacionHijo(at1="aaa", at2=33),

    @AnotacionHijo(at1="bbb")

})

public void  ....(){}
//-----------
@Target(ElementType.METHOD)
public @interface AnotacionHijo{
    public String at1();
    public int at2() default 0;
)
//-----------
@Target(ElementType.METHOD)
public @interface AnotacionPadre{
    public AnotacionHijo[] value() default {};
)

Bibliografía


Mas...


Un ejemplo as complejo se lo puede revisar en la parte 3.

Anotaciones en Java - Parte 1

Las anotaciones en Java es un tema bastante nuevo para mi, pero me parece herramienta interesante y pienso que no todos los programadores conocemos.

Introducción

Las anotaciones son una característica de java que aparecen a partir del JDK 5 y nos proveen de información acerca del código fuente y no necesariamente tienen efecto en este. Un ejemplo claro de anotaciones esta en los nuevos frameworks de java como por ejemplo Hibernate, JPA, entre otros.
Para entender de una manera mejor el uso de las anotaciones, explicaré dos anotaciones que se utilizan comúnmente en Java:  @Deprecated y @Override
La primera @Deprecated se utiliza para indicar que un método se encuentra obsoleto, es decir que ha sido reemplazado por otro mas actual o simplemente ya no tiene soporte y @Override en cambio indica que un método heredado será reemplazado por otro.
Este es un ejemplo de la información que se tiene sobre un método y claramente esto no interfiere en la ejecución o la lógica del mismo.

Los casos anteriores son ejemplos básicos, ya que las anotaciones pueden guardar cuanta información se desee, esto se explicará en la siguiente parte.

Definiendo Anotaciones Propias

Primero partamos de la forma de definir una anotación (el siguiente es el código fuente de la definición de la anotación java.lang.Override):

package java.lang;

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

Analicemos los elementos particulares en este caso.

  1. @interface: es la clave para indicar que este archivo ".java" será la definición de una anotación.
  2. @target: especifica en que lugar se puede utilizar esta anotación:
    • ANNOTATION_TYPE: solo pueden utilizarse en la definición de anotaciones por ejemplo: @Target y @Retention son de este tipo.
    • CONSTRUCTOR: en los constructores de una clase.
    • FIELD: para los atributos privados o públicos de una clase.
    • LOCAL_VARIABLE: para las variables declaradas dentro de métodos.
    • METHOD: para métodos de una clase.
    • PACKAGE: para archivos de definición del paquetes.
    • PARAMETER: para parámetros de funciones.
    • TYPE: para las clases, similar a ANNOTATION_TYPE pero aplicable a objetos o clases en general.
  3. @Retention: especifica cuanto se retendrá la anotación, sus casos son:
    • SOURCE: indica que la anotación solo permanecerá en el código fuente, al momento de compilar desaparecerá por así decir. de manera que no es escrita en el ".class"(bytecode).
    • CLASS: se incluye en el momento de compilar, pero se descartan al momento de cargar la clase (ejecutar). Este es el comportamiento por defecto.
    • RUNTIME: no se descarta la anotación, estará disponible para Reflexión en la ejecución. El tema de Reflexión se lo abordará adelante.
  4. Override: es simplemente el nombre de la anotación.
Como indique en la introducción @Override es un caso de una anotación sencilla. Pero las anotaciones pueden servirnos para guardar información adicional.
Por ejemplo si desearíamos crear una anotación que sirva para guardar información del autor e IDE de una clase, podría ser:

package eblug;
import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ClassInfo {

    String autor();
    String comentario() default "";
    int version() default 1;
    IdeEnum ide() default IdeEnum.NETBEANS;
}

public enum IdeEnum {

    NETBEANS,
    ECLIPSE
}

Esta anotación estará restringida para clases (ElementType.TYPE) y estará disponible en tiempo de ejecución (RetentionPolicy.RUNTIME) pero ademas tendrá un atributo llamado autor que será un de tipo String y otros mas como comentario, versión e IDE.

Ojo que no todos los tipos pueden ser utilizados en las Anotaciones
Para utilizar simplemente se agrega a la definición de la clase de la siguientes formas:

@ClassInfo(autor = "eblug")
public class ClaseEjemplo{
}
//----------------------------------------
@ClassInfo(autor = "eblug", comentario = "un comentario")
public class ClaseEjemplo{
}
//----------------------------------------
@ClassInfo(autor = "eblug", ide = IdeEnum.ECLIPSE, version = 2)
public class ClaseEjemplo{
}
Como se aprecia anteriormente el autor es obligatorio en la anotación porque no tiene la definición de default a diferencia de comentario, version e IDE.

Una particularidad de las anotaciones es cuando se tiene un solo atributo y este lleva el nombre de "value" la utilización puede ser la siguiente:

package eblug;
import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ClassInfo {

    IdeEnum value();
}
public enum IdeEnum {
    NETBEANS,
    ECLIPSE
}

En este caso la utilización puede ser así:
@ClassInfo(IdeEnum.ECLIPSE)
public class ClaseEjemplo{
}

No incluyo el resto de la utilización porque me parece que es bastante intuitiva. 

Mas...