29 de enero de 2019

Polimorfismo

El Polimorfismo es uno de los conceptos que siempre suenan cuando se habla de POO junto con la encapsulación o la herencia. Sin embargo aplicarlo no es nada nuevo, llevamos ya muchas sesiones usándolo aunque no hayamos nombrado el concepto.

En resumen: dos objetos de distinto tipo pueden recibir el mismo mensaje (invocación de un método) y responder de dos (Poli) formas (morfia) distintas.

Polimorfismo: objetos de distinto tipo pueden recibir el mismo mensaje y responder de forma distinta


¿Dónde hemos usado polimorfismo?

Te invito a que pienses un momento sobre si hemos utilizado una misma invocación a distintos objetos y el resultado fue distinto.

Estos ejemplos ya vistos usan polimorfismo:
  1. Método toString() de todos los objetos que hemos visto (Integer, String, Coche, etc...) todos nos devuelven un String, pero cada uno lo construye de maneras distintas
  2. Particularmente para nuestros ejemplos de vehículos, unos tienen matrícula, otros ruedas, incluso un barco con su eslora... a todos se les llama con toString() y devuelven un String pero lo calculan de manera distinta
  3. Método setColor(String color): Ahora la invocación añade el argumento color. Podíamos poner el color que quisiéramos hasta que instanciamos nuestra Harley-Davidson que sólo aceptaba dos colores. Pasó de una simple asignación a avisarnos que hay limitaciones con ese objeto = polimorfia.
  4. Veremos más ejemplos cuando lleguemos a las Interfaces viendo como tipos sin relación responden distinto a un mismo mensaje.

Esta es la entrada más corta que he hecho y quiero dejarla así porque la próxima vez que oigas el concepto polimorfismo no quiero que te eches las manos a la cabeza, quiero que pienses que es uno de los conceptos más simples pero potentes de la POO.

28 de enero de 2019

Clases anónimas

Terminábamos nuestra sesión de clases abstractas preguntándonos ¿por qué una clase abstracta (que no se puede instanciar) tiene constructores?

Obviando que los constructores declarados en VehiculoConRuedas sirven para aprovechar constructores de Vehiculo, lo correcto es que las clases abstractas contengan todo el código relacionado con sus miembros.

Es normal que el código que implementemos para inicializar los atributos modelo y color, pueda heredarlo para no tener que implementarlo en cada subtipo (mejorando el mantenimiento como ya hemos repetido varias veces)

No obstante, hay un caso muy importante para tener también constructores incluso en clases abstractas: instanciar con clases anónimas.

Una clase anónima nos va a permitir declarar una clase e instanciarla en la pequeña parte de código donde nos haga falta, haciendo nuestro código más legible y económico. Como podemos deducir de su definición, es una clase que no tiene nombre, no está contenida en su propio archivo .java, ni es interna (esto último todavía no lo hemos visto)

Una clase anónima nos va a permitir declarar una clase e instanciarla brevemente, haciendo nuestro código más legible y económico


Hasta ahora hemos visto que cada clase tenía que tener su propio archivo .java con el mismo nombre o Eclipse nos daba un error (MiClase tiene el código en el archivo MiClase.java).

Sin embargo, vamos a encontrarnos infinidad de situaciones en las que necesitaremos un objeto que podría usar un tipo prácticamente igual a otro ya creado, salvo por un pequeñísimo cambio que además sólo vamos a utilizar en ese objeto en un punto concreto de nuestro código y nunca más nos interesará.

Es demasiado repetitivo y excesivo tener que crear un archivo para una nueva clase que herede de un tipo sólo para instanciar un objeto en una línea de código (imaginad crear un botón en un menú donde lo único que cambia con otro botón es qué tiene que pasar al hacer click... ¿nos creamos una clase para cada botón?). Para estos casos, Java tiene las clases anónimas que son una herramienta poderosísima de personalización de un tipo.

Una clase anónima se crea al vuelo a la hora de instanciar un objeto que necesitamos y no encaja exactamente con ningún tipo. Se define llamando al constructor de un tipo conocido y añadiéndole un cuerpo con el comportamiento personalizado.

Vamos a ver un ejemplo con nuestro código sobre Vehiculo creándonos un triciclo. No nos vamos a crear una nueva clase Triciclo, pues sólo queremos imprimir nuestro objeto triciclo, pero no encaja con ningún tipo que tengamos: tiene tres ruedas (no es una Moto) y no tiene matrícula (no es un Coche).
NOTA: No es una buena práctica utilizar tipos que tengan todo lo que necesitamos y hacer null el resto de atributos. Los objetos deben ser del tipo con el que se construyen, el resto son apaños que aumentan la deuda técnica.

Lo primero es seleccionar la clase que más se acerca a lo que queremos: VehiculoConRuedas. Éste sería el código para crearme un triciclo:
VehiculoConRuedas triciclo = new VehiculoConRuedas("Fisher-Price", "Multicolor") {
    
    @Override
    public int getNumeroDeRuedas() {
        return 3;
    }

};
Vemos que declaramos una variable del tipo VehiculoConRuedas (pues hereda de éste tipo), pero al ser una clase abstracta y no poderse instanciar (está incompleta) directamente nos añadirá lo que le falta por implementar: getNumeroDeRuedas().

Si imprimimos nuestro triciclo veremos que el método toString() funciona igual que en las clases NO anónimas Coche y Moto que heredaban de VehiculoConRuedas. Tenemos la siguiente salida.

Fisher-Price (Multicolor), 3 ruedas

¿Sólo puedo modificar métodos que ya existan?

No. Al ser VehiculoConRuedas una clase abstracta debemos implementar los métodos no implementados, pero podemos añadir más miembros o modificar (incluso reutilizar) los que ya estén implementados.

En nuestros ejemplos anteriores nos hemos creado una Moto. Vamos a crearnos otra que sea una Harley-Davidson. Ésta marca de moto sólo permite dos colores: negro y rojo. Vamos a cambiar el método setColor(String) para que no acepte ningún color que no sea uno de esos dos:
Moto harley = new Moto("Harley-Davidson", "Rosa") {

    @Override
    public void setColor(String color) {
        if(!(color.equals("Rojo") || color.equals("Negro"))) {
            System.out.println("No se permite ese color para " + modelo);
        } else {
            super.setColor(color);
        }
    }
    
};
System.out.println(harley);
Con este código tenemos la salida:

No se permite ese color para Harley-Davidson
Moto: Harley-Davidson (null), 2 ruedas

Nos avisa que el color no está permitido y de hecho vemos que nos imprime null porque no se ha asignado. Si le asignamos (reutilizando con super el método que hereda) un color válido si que funcionará:
harley.setColor("Negro");
System.out.println(harley);
Moto: Harley-Davidson (Negro), 2 ruedas

¿Necesito una clase abstracta para crear una clase anónima?

No. Vamos a crearnos un barco usando la clase Vehiculo que no es abstracta:
Vehiculo barco = new Vehiculo("CMB Yachts", "Blanco") {
    double eslora = 47.8;

    private double getEslora() {
        return eslora;
    }
    
    @Override
    public String toString() {
        return super.toString() + " con " + getEslora() + "m de eslora";
    }
    
};
System.out.println(barco);
Vemos que podemos tanto sobrescribir, como crear nuevos miembros (incluyendo atributos como eslora) y no afecta si el tipo del que heredamos es abstracto o no. Incluso podemos crear clases anónimas usando una Interface como veremos cuando las conozcamos. El resultado por consola es:

CMB Yachts (Blanco) con 47.8m de eslora

Las clases anónimas y el concepto scope

Un aspecto importante a destacar es que las clases anónimas se crean en el ámbito (scope) de otra y tienen acceso a los miembros tanto de su clase como del tipo donde se crean incluso con el modificador de acceso private. Esto nos permite referenciar valores que permanecen privados pero que pueden ser usados en la implementación de nuestra clase anónima (por ejemplo un botón puede desencadenar un cambio en un componente privado de un formulario).

Las clases anónimas tienen acceso a todos sus miembros y a los del tipo donde se crean


Vamos a ver un ejemplo. Declaro un atributo privado:
private static int miCodigoSecreto = 12345; // ¿En serio?
Pero si me instancio una clase anónima dentro de su ámbito voy a tener acceso:
Coche cocheConPin = new Coche() {

    @Override
    public String toString() {
        return "El código secreto es: " + miCodigoSecreto;
    }
    
};
System.out.println(cocheConPin);
Si imprimo mi nuevo objeto tendré la salida:

El código secreto es: 12345

Siendo anónimas ¿Cómo nos referimos a su class?

Las clases van a tomar por defecto el nombre del tipo donde se instancian más un autonumérico precedido de un dolar ($). Podemos verlo con el siguiente código:
System.out.println(triciclo.getClass());
System.out.println(harley.getClass());
System.out.println(barco.getClass());
Como vemos nos salen 3 clases distintas (instanciadas en la clase Igualdad) y lo único que cambie es el autonumérico:

class Igualdad$1
class Igualdad$2
class Igualdad$3

Conclusión

Puede que todavía no veas las posibilidades que aportan las clases anónimas, pero cuando conozcas los Listener, las lambdas, los stream, las Interfaces Funcionales y otras capacidades de Java, no querrás seguir programando sin ellas.

25 de enero de 2019

Clases abstractas

Las clases abstractas son clases pensadas para ser heredadas de ellas y que encapsulan código dirigido . Se comportan exactamente igual que las clases que hemos visto hasta ahora con estas diferencias:
  1. Debe declararse que son abstractas usando en su declaración la palabra reservada abstract como modificador.
  2. Tendrán métodos abstractos que se declararán también con abstract. Estos métodos no tienen cuerpo y serán implementados por sus subtipos.
  3. Al carecer de una implementación completa (los métodos abstractos no están implementados), no podrán construirse instancias de este tipo pues para poder crear objetos deben tener todos sus miembros implementados.
NOTA: Si bien el código no nos arrojará ningún error ni nos obligará a cumplir las condiciones 2 y 3, el hecho de no cumplirlas sería un indicativo de que no tiene sentido declararla abstracta.

Con las clases abstractas vamos a tener código relacionado en un único sitio para que por herencia lo completen y aprovechen sus subtipos


Como todo se ve mejor con un ejemplo, vamos a crearnos una clase abstracta llamada VehiculoConRuedas, que servirá para heredar de Vehiculo y gestionar lo que tenga que ver con ruedas. Vamos a crearla de la misma forma que lo hicimos la sesión anterior, pero en esta ocasión, aunque pensemos que debemos escoger el atributo numeroDeRuedas, no lo vamos a marcar y sólo le vamos a poner el nombre "VehiculoConRuedas" a la clase abstracta.
NOTA: Si ya tuviera más clases con comportamientos a agrupar (por ejemplo ya tuviera creada Moto), Eclipse nos permite añadir todas las clases de las que queramos extraer la superclase.
public class VehiculoConRuedas extends Vehiculo {

  public VehiculoConRuedas() {
    super();
  }

  public VehiculoConRuedas(String modelo, String color) {
    super(modelo, color);
  }

}
Eclipse ha insertado en nuestra jerarquía la clase VehiculoConRuedas entre Coche y Vehiculo para que todo siga funcionando correctamente. Todo esto también podríamos haberlo escrito a mano y no debe dejar de practicarse para coger soltura.

Si abrimos nuestra nueva clase vemos que sólo tiene dos constructores y que es una clase normal. Vamos a añadirle un método abstracto para ver qué una clase normal no lo permite. Lo haremos simplemente declarándolo incluyendo el modificador abstract y en vez de un cuerpo terminaremos con punto y coma:
public abstract int getNumeroDeRuedas();
Tendremos un error en Eclipse que nos dirá que quitemos el modificador abstract del método o que hagamos la clase abstracta. Vamos a declarar nuestra nueva clase abstracta y ahora ya no tendremos errores.

Nuestra clase completa queda:
public abstract class VehiculoConRuedas extends Vehiculo {

  public VehiculoConRuedas() {
    super();
  }

  public VehiculoConRuedas(String modelo, String color) {
    super(modelo, color);
  }

  public abstract int getNumeroDeRuedas();
}
Guardamos los cambios en VehiculoConRuedas y volvemos al código de Coche para ver que hereda de VehiculoConRuedas (lo hizo Eclipse) pero nos aparece un error: No tiene implementado el método getNumeroDeRuedas().

Podemos usar Eclipse para que nos añada este método vacío o hacerlo a mano. Si aceptamos la solución que nos ofrece Eclipse (Add unimplemented methods) cuando nos situamos encima del error vamos a obtener en Coche el siguiente código (seguramente estará añadido al final de todo):
@Override
public int getNumeroDeRuedas() {
  // TODO Auto-generated method stub
  return 0;
}
Con esto Eclipse nos crea ese método vacío y nos añade una tarea (//TODO) para rellenar la implementación. Como nosotros tenemos precisamente un atributo llamado numeroDeRuedas vamos a devolver su valor como hacemos con cualquier otro getter:
@Override
public int getNumeroDeRuedas() {
  return numeroDeRuedas;
}
Ahora si imprimimos un coche veremos que el método toString() que implementamos funciona correctamente:
  1. Obtiene el modelo y color desde Vehiculo
  2. Obtiene el número de ruedas usando u miembro heredado de VehiculoConRuedas
  3. El método toString() está implementado en Coche.
Compruébalo añadiendo este código:
System.out.println(coche1);
A nuestro main hecho en la sesión de igualdad, verás que nos muestra por consola:

Placa 1234 BBB - Seat Ibiza (Rojo), 4 ruedas

Reutilizamos nuestra jerarquía

Ahora llega el momento de reutilizar nuestra jerarquía creándonos una clase Moto. Podemos usar las ventajas de Eclipse y decir en la ventana de creación de clase que hereda de VehiculoConRuedas. Así tenemos ya trabajo hecho:
public class Moto extends VehiculoConRuedas {

  @Override
  public int getNumeroDeRuedas() {
    // TODO Auto-generated method stub
    return 0;
  }

}
Nos queda implementar el método abstracto getNumeroDeRuedas(). No es necesario crearse un atributo para completar este método. Bastaría con que siempre devuelva el valor 2 para ver el ejemplo:
@Override
public int getNumeroDeRuedas() {
  return 2;
}

Colocando más cosas en su sitio

Si ahora hago la prueba de imprimir una nueva moto veré que obtengo un resultado bastante feo como ya vimos en la sesión sobre el método toString():
System.out.println(new Moto());
// Devuelve algo como Moto@52e922
Tiene sentido colocar el código que está relacionado en el mismo sitio: Si todos los Vehiculo tienen modelo y color, y todos los VehiculoConRuedas tienen ruedas, el método toString() podría ir mejorándose en función de qué nivel ocupa mi tipo en la jerarquía. Voy a implementarlo haciendo dos cosas:
  1. Crearé un constructor para Moto que acepte modelo y color. (puedes usar Source > Generate Constructors from Superclass
  2. Moveré la parte del código de Coche.toString() a las clases a las que pertenecen los miembros correspondientes.
Aquí están los cambios que hay que hacer:

En Vehiculo:
@Override
public String toString() {
  return modelo + " (" + getColor() + ")";
}
En VehiculoConRuedas:
@Override
public String toString() {
  return super.toString() + ", " + getNumeroDeRuedas() + " ruedas";
}
En Coche:
@Override
public String toString() {
  return "Placa " + matricula + " - " + super.toString();
}
En Moto:
public class Moto extends VehiculoConRuedas {

  public Moto(String modelo, String color) {
    super(modelo, color);
  }

  @Override
  public int getNumeroDeRuedas() {
    return 2;
  }

  @Override
  public String toString() {
    return "Moto: " + super.toString();
  }

}
Ahora si imprimo los ejemplos de toString() anteriores:
System.out.println(coche1);
System.out.println(new Moto("Suzuki", "Negro"));
Tengo la salida:

Placa 1234 BBB - Seat Ibiza (Rojo), 4 ruedas
Moto: Suzuki (Negro), 2 ruedas

Queda demostrado el ahorro de tiempo tanto construyendo como manteniendo nuestro código si nos apoyamos en jerarquías que me permitan tener el código relacionado escrito en un único sitio para que luego sea aprovechado de manera particular en otras clases más especializadas.

Si alguno ha examinado el código quizás pueda preguntarse: Si las clases abstractas no se pueden instanciar ¿Por qué VehiculoConRuedas tiene constructores?

Es una buena pregunta, vamos a verlo mejor en la sesión que explica qué son las clases anónimas.

24 de enero de 2019

Herencia

Ya he adelantado que las clases pueden heredar de otras clases. Esta capacidad de heredar nos va a servir para que clases relacionadas que deben tener miembros parecidos o repetidos no tengan que ser implementados para cada clase. Si una clase superior (superclase o clase padre) tiene una serie de miembros implementados, pueden ser aprovechados por clases que hereden de ella (subtipo o clase hija).

Con la herencia reutilizamos código de clases fuertemente relacionadas, estableciendo una relación "es un" o de especialización


Se van a heredar todos los miembros (no los constructores), pero su visibilidad lo regirá el control de acceso que marquemos (un miembro private no se podrá ver desde su heredero como ya vimos, deberá ser marcado mínimo como protected para tener acceso desde un heredero)

Para poder definir y usar la herencia vamos a usar las palabras reservadas extends y super:
  • extends: sirve para definir la clase de la que se hereda (superclase) en la propia cabecera de la clase que hereda (subtipo).
  • super: sirve para referirnos a miembros de la superclase (parecido a como hacíamos con this)
Vamos a verlo con el código. En nuestro ejemplo vamos a crear una clase padre con nombre Vehiculo que nos sirva también para aeronaves, embarcaciones, etc... y decidimos que lo que deben tener todos los vehículos es un modelo y color. La forma de hacerlo es extraer los miembros que tienen que ver con modelo y color y moverlos a nuestra nueva clase Vehiculo.

Lo más fácil usando Eclipse es usar las ayudas que nos ofrece este IDE. Si hacemos click derecho sobre la Coche (en el Package Explorer o directamente en la ventana de su código fuente) elegimos Refactor > Extract Superclass... y en la ventana que nos aparece le ponemos el nombre Vehiculo y elegimos en la parte de abajo los miembros que vamos a extraer. Estos son: modelo, color, getColor(), setColor(String). Al clickar en Finish tendremos nuestra superclase creada (ver en su fichero .java).
public class Vehiculo {

    protected String modelo;
    String color;

    public Vehiculo() {
        super();
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

}
Ahora vemos que Coche carece de esos miembros (en su código ya no aparecen) y lo que debemos hacer es declarar que herede de Vehiculo con la palabra reservada extends. Eclipse ya ha hecho ese trabajo por nosotros. Si hacemos la refactorización a mano no nos podemos olvidar de declarar esta herencia:
public class Coche extends Vehiculo {

  // Codigo sin los miembros de Vehiculo

}
Ahora sí que son miembros que conoce la clase Coche. Y los podremos usar de la misma forma que ya los usábamos. Puedes comprobarlo con el código que hicimos en la sesión de igualdad de objetos.

De hecho vamos a cambiar un poco el código para ver cómo los valores de tipo Coche SON también un Vehiculo:
public class Igualdad {

  public static void main(String[] args) {
    Coche coche1 = new Coche("Seat Ibiza", "Rojo");
    coche1.setMatricula("1234 BBB");
    //...
    Vehiculo coche3 = coche1;
    // coche3.setMatricula("5678 CCC");// error!

    //...
  }
}
Aquí vemos que coche1 puede asignarse a una variable de tipo Vehiculo y el resto de código funciona correctamente. Es una buena práctica acostumbrarse a usar los tipos más básicos de lo que necesito: como sólo uso los miembros de modelo y color, me sirve un tipo Vehiculo. Sin embargo si quisiera usar el método setMatricula(String) sí que tendría que usar una variable de tipo Coche, porque sólo así tendré acceso a ese método como ya os adelantaba en la sesión donde construíamos objetos. Queda demostrado entonces que las variable dan acceso a los miembros de su tipo, independientemente de que el valor sea de un subtipo más especializado.

Con la herencia se crea una jerarquía de tipos más especializados compatibles con sus ancestros


Aprovechando los constructores

Hemos dicho que se heredan los miembros pero no los constructores: ¿Cómo aprovecho el código que ya tenía en los constructores?

Lo que hacemos es acceder a miembros de las clases superiores o sus constructores. Voy a crearme un constructor de Vehiculo con los dos parámetros modelo y color:
public Vehiculo(String modelo, String color) {
    this.modelo = modelo;
    this.color = color;
}
Ahora puedo llamar al constructor de la superclase para aprovecharlo y sólo añadir código nuevo. En mi caso uso el constructor de Coche con modelo y color, pero voy a vaciarlo del código repetido ahora en Vehiculo. Nuestro constructor pasa de esto:
public Coche (String color, String modelo) {
    super(color, modelo);
    numeroDeRuedas = 4;
}
NOTA: Asigno 4 a numeroDeRuedas pues ya no utiliza el constructor por defecto Coche(). Te propongo que modifiques los otros constructores para que reutilicen éste como vimos en la sesión sobre constructores.


Con la palabra reservada super hacemos referencia a la superclase (igual que con this hacemos referencia a sí mismo). Si usamos el asistente de contenido veremos que nos salen los miembros de la superclase a los que tengo acceso: modelo, color, getColor(), setColor(String) y todos los de Object que es "el abuelo".

Con lo que hemos visto aquí ya tenemos nuestra primera jerarquía de clases. En la siguiente sesión sobre clases abstractas vamos a hacernos otra clase Moto que también herede de Vehiculo, pero en medio, para reutilizar el código que tenga que ver con el número de ruedas, crearemos la clase abstracta VehiculoConRuedas.

Sentencia switch

La sentencia switch nos va a servir para el control del flujo de nuestra aplicación.

Al contrario que if que ejecuta un bloque si se cumple una expresión (boolean) y si añadimos else se ejecuta otro bloque si no se cumple, con switch podemos tener varios caminos a seguir evaluando una expresión de otros tipos y delegar la ejecución sobre el caso que encaje, entre una variedad de ellos (no sólo true o false).

He puesto esta entrada en esta parte menos usada porque, según mi experiencia, realmente se usa poco. Esto tiene fácil explicación:
  1. Su sintaxis se suele olvidar por usarse poco, ser bastante larga y dar bastante pereza.
  2. Normalmente con unos pocos casos usando if/if-else conseguimos lo mismo con mayor rendimiento.
  3. Usar un Map para recuperar valores (resultados de un switch) por una clave (casos case) mejora el mantenimiento.
  4. Tiene limitaciones en los tipos que se pueden usar para los casos (básicamente int, String, y Enum) .
  5. Seguramente, conscientes de esto en Java 12 mejoran su sintaxis.
Pero para que no quede sin ver una sentencia que se puede encontrar en la mayoria de lenguajes de programación vamos a ver su sintaxis y un ejemplo.

La sentencia switch se define usando la palabra reservada switch, estableciendo un valor a evaluar (expresion) y varios apartados conteniendo cómo proceder en función del valor evaluado (case) que puede declarar al final qué hacer en caso de no encontrar coincidencias (caso default):

Nos hacemos el siguiente método:
static void testSwitch(int expresion) {
  switch (expresion) {
  case 1:
    System.out.println("Caso 1");
    break;
  case 2: case 3:
    System.out.println("Caso 2 o 3");
    //break; Si omitimos "break" sigue ejecutando
  case 4:
  case 5:
    System.out.println("Caso 4 o 5");
    break;
  default:
    System.out.println("Ninguno caso encaja");
    break;
  }
}
Podemos ver que hay una expresion de tipo int y cuatro casos (case) que se pueden ejecutar en función de su valor:
  1. case 1: Cuando expresion tenga valor 1
  2. case 2: case 3: Cuando expresion tenga valor 2 o 3
  3. case 4:
    case 5:
    Cuando expresion tenga valor 4 o 5
  4. default: Cuando no sea ninguno de los anteriores
Fijaros que para el case 2: case 3: NO hemos puesto break antes del siguiente case. No debemos olvidarnos de ponerlo si no queremos que se siga ejecutando todo el código del switch hasta que se encuentre un break o termine el bloque switch.

Al probar este método con el siguiente código:
testSwitch(0);
testSwitch(1);
testSwitch(3);
Tenemos la siguiente salida:

Ninguno caso encaja
Caso 1
Caso 2 o 3
Caso 4 o 5

Esto es el resultado de:
  • Ninguno caso encaja: 0 (cero) no está entre las opciones elegidas y se ejecuta default.
  • Caso 1: coincide y ejecuta case 1 hasta el break.
  • Caso 2 o 3 + Caso 4 o 5: coincide con case 3 pero al no haber puesto break se sigue ejecutando lo siguiente hasta que encuentra otro break.
Espero que haya quedado claro, si no, usa los comentarios.

15 de enero de 2019

Gradle + Eclipse + GitHub

En esta entrada voy a enumerar los pasos que hay que seguir para poder utilizar, en nuestro IDE Eclipse, un proyecto de Gradle con una dependencia directa de un repositorio en GitHub.

Con esto vamos a poder automatizar la construcción y pruebas de nuestra aplicación y tener las dependencias actualizadas (o establecerlas a una versión concreta) sin tener que cargar con ellas.

Eclipse, Gradle y GitHub forman una combinación perfecta para automatizar tareas de construcción y pruebas


Eclipse lo conocemos, GitHub también deberíamos, la nueva desconocida puede ser Gradle: es una herramienta de automatización de la construcción de código abierto. En esta entrada se verán términos como plugins o tasks, pero no se explican pues no es objeto de la entrada. Para más información se puede ver en su documentación.

Hay más herramientas de automatización de tareas como Maven y Ant de Apache. En Java personalmente creo que Gradle se está volviendo más fuerte, pero es una opinión. Maven y Ant llevan más tiempo y es probable que si cogéis proyectos más antiguos los usen.

En el ejemplo vamos a usar el repositorio en GitHub "jsonbeans" de EsotericSoftware para convertir objetos entre Java y JSON, pero serviría para cualquier otro. Nos vamos a traer la versión release de la rama master.

Para ello vamos a necesitar Eclipse con el plugin Gradle que viene por defecto en la versión que instalamos con nuestro tutorial y utilizar el servicio Jitpack que será el encargado de construir y suministrarnos bajo demanda artefactos (.jars) de repositorios de GitHub (o de otras plataformas parecidas) para cubrir esas dependencias.

Creamos un proyecto vacío siguiendo estos pasos:
  1. Abrimos Eclipse
  2. En Package Explorer: New/Project.../Gradle Project/Next
  3. Poner nombre al proyecto -> Next -> Next o escoger versión/Wrapper -> Finish
Con esto ya tenemos un proyecto "vacío" creado con una configuración por defecto. Vamos a personalizarlo para lo que queremos conseguir:
  1. Copiar la configuración del archivo build.gradle preparado y substituirlo (se añaden una serie de plugins y dependencias que se explican en comentario dentro del propio archivo)
  2. Modificar en build.gradle la clase con el main según corresponda: cambiar App a com.paquete.Library por ejemplo
Ahora tenemos que hacer que esos cambios sean visibles por Eclipse:
  1. Actualizar las tareas (tasks) del proyecto Gradle en pestaña "Gradle Tasks" (click en icono de refresco)
  2. Ahora nos aparecen nuevas tasks por los plugins que instalamos. Ejecutar la task (haciendo doble click sobre ella) ide/eclipse
  3. Refrescar el proyecto en el Package Explorer
  4. Comprobar que aparece jsonbeans-master.jar en Referenced Libraries
  5. Elegir para el proyecto la librería JDK que tengamos instalada (por ejemplo C:\Program Files (x86)\Java\jdk1.8.0_191). Hay multiples formas de hacerlo, mi preferida para compartir proyectos es configurarlo en el fichero gradle.properties (crearlo si no existe en la raiz del proyecto). Se hace añadiendo una línea (ejemplo):
org.gradle.java.home=C:/Program Files (x86)/Java/jdk1.8.0_191

NOTA 1: Es importante usar "/" en la ruta dentro de gradle.properties.
NOTA 2: Si al seguir estos pasos da un error, puede ser debido a la ubicación del archivo gradle.properties. Aunque en la documentación dice que debe ir en el raíz, se ha comprobado que algunas veces se soluciona un error en este procedimiento ubicándolo en la carpeta "src".

Comprobamos que todo ha ido bien y que el asistente de contenido busca en las nuevas dependencias (en Library mismo probar la clase Json). Podemos usar este código para el main:
public static void main(String[] args) {
    // Serializador JSON
    Json json = new Json(OutputType.json);

    // Objeto para serializar inicializado
    Object obj = new Object() {
        int[] numeros;
        String texto;

        Object init() {
            numeros = new int[] { 1, 2, 3 };
            texto = "Mi texto";

            return this;

        }
    }.init();

    // Salida por consola serializada en formato facil de leer
    System.out.println(json.prettyPrint(obj));
}

NOTA: Si el código te parece extraño, mira la explicación. Se ha puesto como ejemplo para crear un objeto asignándole unos valores NO por defecto a una clase anónima con el mínimo código)

Finalmente ejecutamos para ver el resultado:
  1. Ejecutar task application/run y comprobar que funciona

De esta forma vamos a poder compartir muy fácilmente el proyecto con sus dependencias sin tener que compartir los archivos compilados. Gradle se encargará por nosotros de controlar de manera centralizada los archivos de esas dependencias para todos los proyectos que gestionen (los cacheará en la carpeta GRADLE_HOME (se usará la instalación de Gradle local o la carpeta .gradle en la carpeta personal del usuario)

Compárteme

Entradas populares