‘generics’

Generics – Algunos Apuntes

Thursday, June 17th, 2010 Autor : gmateo

Código Fuente: Ejemplo 8

a) No se puede usar tipos parámetro, para reemplazar a tipos primitivos.

b) Una clase genérica es compartida por todas sus invocaciones:

Set enteros = new HashSet();














Set doubles = new HashSet();














System.out.println("-->"+(enteros.getClass()==doubles.getClass()));














En este ejemplo se imprime “true”, ya que todas las instancias de una clase genérica tienen la misma clase en tiempo de ejecución sin importar el valor del parámetro actual.

c) No se debe usar los tipos de parámetros al hacer “instanceof” ni “cast”.

   1: Set numeros = new HashSet();


   2: //if(numeros instanceof HashSet){}


   3: Set numerosEnteros = (HashSet)numeros;

 

Línea 2 : No va a compilar ya que no se puede usar esa expresión con “instanceof”

Línea 3: Si compila pero es innecesario ya que es equivalente a: “(HashSet) numeros”. Va a generar un “unchecked warning”

d) Desde la versión 5, Class es genérico, por tanto el tipo de “Integer.class” es “Class”. De esta manera ahora podemos hacer lo siguiente:

   1: Class stringClass = String.class;


   2: try {


   3:     String nuevoString = stringClass.newInstance();


   4: } catch (Throwable e) {


   5:     e.printStackTrace();


   6: }

Notar que en la línea 3 ya no necesitamos hacer cast.

e) Con respecto a la clase Exception tenemos lo siguiete:

   1: public class ExcepcionEspecificaextends Throwable> extends Throwable{


   2:     public void prueba() {


   3: //        try {


   4: //        }catch(T e){


   5: //        }


   6:     }


   7:     public void prueba2() throws T{


   8:     }


   9: }

Línea 1 : Es inválida ya que ninguna clase genérica puede extender de “Throwable”

Línea 4 : Es inválida ya que no se puede poner un tipo genérico en el catch.

Línea 7: Si es válido ya que sí se puede colocar un tipo genérico en la zona de “throws”.

f) No se puede crear arrays de tipos genéricos.

Cómo está implementado el soporte para Generics ?

Thursday, June 17th, 2010 Autor : gmateo

Código Fuente: Ejemplo 7

El soporte para generics en Java está implementado como una conversión "front-end" llamada "erasure".

De una manera simplista  podemos decir que se realiza una transformación del código fuente que tiene los datos genéricos a una versión sin datos genéricos (en esta versión se incluyen todos las cast que sean necesarios) . Por tanto en el virtual machine no hay tipos genéricos, todos los objetos son clases ordinarias.

Por ejemplo:

public class Contenedor<E> {
public void agregarElementos(E elemento){

}
}

Será transformada en:

public class Contenedor {
public void agregarElementos(Object elemento){

}
}

De esto debemos inferir:

a) Que por más que tengamos: Contenedor<String>, Contenedor<Integer>, etc, al final en el virtual machine sólo tendremos la clase Contenedor.

b) Que es imposible colocar el tipo “E” en atributos o métodos estáticos, ya que el tipo “E” afecta a la instancia y no a la clase en general

Otro ejemplo que podríamos usar sería:

public class ManejadorDeCadenas<T extends Appendable & Serializable> {
public void agregar(T subCadena){
}
}

Que será transformado en:

public class ManejadorDeCadenas {
public void agregar(Appendable subCadena){
}
}

Acá debemos tener en cuenta que “T” será reemplazado por el tipo que esté más cerca a él en este caso “Appendable”. Obviamente que internamente se generarán todos los cast a “Serializable” que sean necesarios.

Ejemplo: Múltiples condiciones que debe cumplir el Tipo

Thursday, June 17th, 2010 Autor : gmateo

Código Fuente: Ejemplo 6

Se tiene las siguientes interfaces y clases:

public interface Mamifero {
}

public interface Cuadrupedo {
}

public class Mono implements Mamifero{
}

public class Perro implements Mamifero,Cuadrupedo{
}

Se desea crear 2 métodos:

1) Un método que permita contar a todos los mamíferos.

2) Un método que permita contar a todos los que sean mamíferos y a la vez cuadrúpedos.

   1: public class ContadorDeAnimales {

   2:     public <T extends Mamifero> int contarMamiferos(List<T> mamiferos){

   3:         return mamiferos.size();

   4:     }

   5:     public <T extends Mamifero & Cuadrupedo> int contarMamiferosYCuadrupedos

   6:                                                  (List<T> mamiferosCuadrupedos){

   7:         return mamiferosCuadrupedos.size();

   8:     }

   9: }

Acá debemos notar el símbolo “&” en la línea 5, esto nos permite señalar más de una condición que debe cumplir el tipo T, en esta línea estamos diciendo que “T” debe ser un subtipo tanto de “Mamifero” como de “Cuadrupedo”.

Y la manera cómo serían invocados sería:
 
   1: List<Perro> perros = new ArrayList();

   2: List<Mono> monos = new ArrayList();

   3: ContadorDeAnimales contador = new ContadorDeAnimales();

   4: contador.contarMamiferos(perros);

   5: contador.contarMamiferos(monos);

   6: contador.contarMamiferosYCuadrupedos(perros);

   7: //contador.contarMamiferosYCuadrupedos(monos);

La línea 7 no compilará ya que “Mono” no implementa las 2 interfaces necesarias.

Ejemplo: Encontrar el valor mínimo de una Lista

Thursday, June 17th, 2010 Autor : gmateo

Código Fuente: Ejemplo 5

El problema es hacer un método que  nos devuelva el mínimo elemento de una Lista. Asumamos que la lista está definida como sigue:

List<Integer> enteros = Arrays.asList(9,5,2,7,8,1);

Primera Solución

   1: public class ListaUtils {

   2:     public static Object min(List lista){

   3:         Object minimo = lista.get(0);

   4:         for (Object elemento : lista) {

   5:             Comparable comparable = (Comparable) elemento;

   6:             if (comparable.compareTo(minimo)==-1){

   7:                 minimo = comparable;

   8:             }

   9:         }

  10:         return minimo;

  11:     }

y lo usaríamos así:

Integer minimo = (Integer) ListaUtils.min(enteros);
System.out.println("Mínimo:"+minimo);

Ahora veamos los problemas de esta solución:

Línea 5 : Para poder realizar las comparaciones necesitamos que los objetos implementen Comparable, por eso es que acá estamos haciendo un cast. Si los elementos no implementan esta interface recién el error saldrá en tiempo de ejecución, debemos modificar la definición del método para forzar que estos errores salgan al compilar.

Seguda Solución

   1: public static Object minConGenerics(List<Comparable> lista){

   2:     Object minimo = lista.get(0);

   3:     for (Comparable elemento : lista) {

   4:         if (elemento.compareTo(minimo)==-1){

   5:             minimo = elemento;

   6:         }

   7:     }

   8:     return minimo;

   9: }

Y lo usaríamos así:

   1: //        minimo = (Integer) ListaUtils.minConGenerics(enteros);

   2: //        System.out.println("Mínimo Con generics:"+minimo);

 

Pero como estas líneas no compilan tenemos que comentarlas.

No compilan ya que el método “minConGenerics” está esperando un tipo Lista<Comparable> y ya sabemos que List<Integer> no es un subtipo del primero, así que no podremos usar ese método.

Tercera Solución

   1: public static <T extends Comparable> T minConGenerics2(List<T> lista){

   2:     T minimo = lista.get(0);

   3:     for (T elemento : lista) {

   4:         if (elemento.compareTo(minimo)==-1){

   5:             minimo = elemento;

   6:         }

   7:     }

   8:     return minimo;

   9: }

Lo usaríamos así.

minimo = ListaUtils.minConGenerics2(enteros);
System.out.println("Mínimo Con generics 2:"+minimo);
 
Línea 1: Acá estamos indicando que todos los elementos de la “lista” son de tipo “T”, también estamos definiendo que T es un subtipo de “Comparable”, y que este método retornará un objeto de tipo “T”
Línea 4:  Como no hemos puesto un tipo a Comparable, vemos que el método “compareTo” podría recibir cualquier objeto como parámetro, y no se forzaría que el parámetro sea del tipo T, vamos a forzar esto en la siguiente solución.

Cuarta Solución

   1: public static <T extends Comparable<T>> T minConGenerics3(List<T> lista){

   2:     T minimo = lista.get(0);

   3:     for (T elemento : lista) {

   4:         if (elemento.compareTo(minimo)==-1){

   5:             minimo = elemento;

   6:         }

   7:     }

   8:     return minimo;

   9: }

Lo usaríamos así:
 
minimo = ListaUtils.minConGenerics3(enteros);
System.out.println("Mínimo Con generics 2:"+minimo);

Línea 1 : Acá estamos usando Comparable<T> para forzar que el “compareTo” de la línea 4 fuerce a que el parámetro que recibe sea de tipo “T”.

Quinta Solución

public static <T extends Comparable<? super T>> T minConGenerics4(List<? extends T> lista){
T minimo = lista.get(0);
for (T elemento : lista) {
if (elemento.compareTo(minimo)==-1){
minimo = elemento;
}
}
return minimo;
}

Lo usaríamos así:
 
minimo = ListaUtils.minConGenerics4(enteros);
System.out.println("Mínimo Con generics 2:"+minimo);
 
Esta quinta solución sólo se hizo teniendo en cuenta la regla de definir los métodos de la manera más genérica posible para de esa manera maximizar el uso de la misma.
En general siempre que se pueda se debería reemplazar los tipos con wildcards “?”.

Wildcards con “super”

Wednesday, June 16th, 2010 Autor : gmateo

Código Fuente: Ejemplo 4

"? super E" nos permite indicar un tipo que es un súper tipo de E, obviamente incluyendo a E. Veamos el siguiente ejemplo:

Esta es la declaración del método addAll de la clase: Collections

   1: public static <T> boolean addAll(Collection<? super T> c, T... elements) {

   2:     boolean result = false;

   3:     for (T element : elements)

   4:         result |= c.add(element);

   5:     return result;

   6: }

Teniendo en cuenta esta declaración veamos las siguientes sentencias:

Declararemos las siguientes listas:

List<Perro> perros = new ArrayList();
List<Mamifero> mamiferos = new ArrayList();
List<Animal> animal = new ArrayList();

Ahora veamos las posibles combinaciones:

1) Caso

Collections.addAll(perros,new Perro[]{});
Collections.addAll(mamiferos,new Perro[]{});
Collections.addAll(animal,new Perro[]{});

Todas las siguientes sentencias son válidas ya que para este caso T=Perro, y por tanto el primer parámetro puede ser cualquier colección que contengas elementos cuyo tipo sea un súper tipo de Perro.

2) Caso

   1: //Collections.addAll(perros,new Mamifero[]{});

   2: Collections.addAll(mamiferos,new Mamifero[]{});

   3: Collections.addAll(animal,new Mamifero[]{});

Para este caso T = Mamifero, por tanto la línea 1 no es válida ya que se está tratando de ingresar una colección cuyos elementos tiene un tipo que no es un súper tipo de Mamifero

3) Caso

   1: //Collections.addAll(perros,new Animal[]{});

   2: //Collections.addAll(mamiferos,new Animal[]{});

   3: Collections.addAll(animal,new Animal[]{});

Para este caso T=Animal, por tanto la línea 1 y 2 no son válidas.

A tener en cuenta

1) Si declaramos una estructura que tenga tipos = “? extends E” no podremos agregar nada a esa estructura, salvo null. Por eso la línea 2 y 3 son inválidas.

   1: List<? extends Number> numeros = new ArrayList(Arrays.asList(1.4,1,2,3));

   2: //numeros.add(10);

   3: //numeros.add(1.2);

   4: numeros.add(null);

2) Si declaramos una estructura que tenga tipos = “? super E” si podemos agregar elementos a la estructura.

List<? super Number> enteros = new ArrayList(Arrays.asList(1,2,3));
enteros.add(10);
enteros.add(1.2);

3) Simplificando podemos decir:

? extends E :  incluye a todos los tipos desde null hasta E

? super E :  incluye a todos los tipos que estén entre E y Object

Obviamente en ambos casos son intervalos cerrados, es decir incluyen a sus extremos.

Wildcards con “extends”

Wednesday, June 16th, 2010 Autor : gmateo

Código Fuente: Ejemplo 3

"? extends E" nos permite indicar un tipo que es un subtipo de E, obviamente incluyendo a E. Veamos el siguiente ejemplo:

Problema

Teniendo en cuenta que tenemos una estructura de herencia. Animal<—Mamífero<—Perrro, necesitamos definir una clase que nos permita pesar cualquier lista de animales.

Solución

Teniendo las clases:

public class Animal {
}

public class Mamifero extends Animal{
}

public class Perro extends Mamifero{
}

La clase encargada de pesar los animales sería:

public class PesadorDeAnimales {
public void pesar(List<? extends Animal> animales){
}
}

Vemos que el método pesar recibe como parámetro cualquier lista que contenga objetos de tipo Animal o cualquier subtipo de este.

PesadorDeAnimales pesador = new PesadorDeAnimales();
List<Perro> perros = new ArrayList();
List<Mamifero> mamiferos = new ArrayList();
List<Animal> animales = new ArrayList();
pesador.pesar(perros);
pesador.pesar(mamiferos);
pesador.pesar(animales);

A tener en cuenta:

Vamos a usar el siguiente ejemplo para mostrar algunos detalles importantes:

   1: List<Integer> listaDeEnteros = Arrays.asList(1,2,3);

   2: //List<Number> listaDeNumeros = listaDeEnteros;

   3: List<? extends Number> listaDeNumeros = listaDeEnteros;

   4: listaDeNumeros.add(new Integer(1));

Línea 2 : Esta línea traería problemas ya que List<Integer> no es un subtipo de List<Number>

Línea 3 : Esta línea si es correcta ya que List<Integer> SI es un subtipo de List<? extends Number>

Línea 4 : Esto va a generar un error, ya que el método add está esperando un parámetro de tipo “? extends Number”, donde ? es un tipo desconocido que puede no ser Integer. Como regla general si una estructura tiene elementos de tipo “? extends” nosotros sólo podremos obtener datos de la misma, pero nunca podremos añadir valores.

Wildcards

Tuesday, June 15th, 2010 Autor : gmateo

Código Fuente: Ejemplo 2

Se llama wildcard al símbolo ?. Y se usa para simbolizar "cualquier tipo".
Para entender este concepto veamos el siguiente problema:

Problema

Se desea crear un objeto sellador que se encargue de sellar cajas que contengan cualquier cosa, frutas, naranjas, objetos, etc. Se debe usar generics.

Solución

Lo primero que podríamos pensar es hacer el siguiente método:

public class SelladorDeCaja {
public void sellarCajaDeObjectos(Caja<Object> cajaDeObjectos){

}

Asumiendo que eso nos permita recibir como parámetro  cualquier tipo de caja, pero recordando que Caja<Naranja> no es un subtipo de Caja<Objeto>, esto no nos servirá, ya que sólo podremos llamar a este método enviando parámetros que estén definidos así:

Caja<Object> cajaDeObjetos = new Caja<Object>();
 
Para resolver este problema necesitamos usar el wildcard con lo cual el método que se encarga del sellado quedaría así:
 
public class SelladorDeCaja {
public void sellarCualquierCaja(Caja<?> cualquierCaja){

}

Con esta declaración ya podríamos llamar al método enviando como parámetro cualquier de las siguientes cajas:
 
Caja<Fruta> cajaDeFrutas = new Caja<Fruta>();
Caja<Manzana> cajaDeManzanas = new Caja<Manzana>();
Caja<Object> cajaDeObjetos = new Caja<Object>();

Generics y Subtyping

Tuesday, June 15th, 2010 Autor : gmateo

Código Fuente: Ejemplo 1

En java un tipo es subtipo de otro si ambos están relacionados por las cláusulas "extends" o "implements". Por ejemplo tenemos que:

Integer es un subtipo de Number
List<E> es un subtipo de Collection<E>

Ahora bien veamos las siguientes declaraciones:

Caja<Fruta> cajaDeFrutas = new Caja<Fruta>();
cajaDeFrutas.poner(new Fruta());
cajaDeFrutas.poner(new Manzana());
cajaDeFrutas.poner(new Naranja());

Vemos que en la “cajaDeFrutas” podemos colocar cualquier Fruta o cualquier subtipo de Fruta (Naranja, Manzana).

Ahora bien, un error común es pensar que como Manzana es un subtipo de Fruta, entonces Caja<Manzana> también es un subtipo de Caja<Fruta>. ESTO ES TOTALMENTE INCORRECTO. Por ejemplo el siguiente código va a dar error en la segunda línea.

Caja<Naranja> cajaDeNaranjas = new Caja<Naranja>();
Caja<Fruta> cajaDeFrutas = cajaDeNaranjas;

Por otro lado, debemos tener en cuenta que en el caso de arreglos todo es distinto ya que ahí si Manzana[] es un subtipo de Fruta[].

Generics Class y Generic Method

Tuesday, June 15th, 2010 Autor : gmateo

Código Fuente: Ejemplo 1

Generics Class

Es una clase que tiene uno o más parámetros tipo.

public class Caja<E> {
List frutas = new ArrayList();
public void poner(E fruta){
frutas.add(fruta);
}
}

Acá vemos el símbolo: <E> lo cual nos indica que “E” es un tipo de parámetro formal, por lo tanto durante la invocación será sustituido por un valor específico. Por ejemplo:

   1: Caja<Manzana> cajaDeManzanas = new Caja<Manzana>();

   2: Caja<Naranja> cajaDeNaranjas = new Caja<Naranja>();

   3: Caja<Fruta> cajaDeFrutas = new Caja<Fruta>();

   4: Caja caja = new Caja();

Línea 1 : Acá “E” será reemplazado por el tipo Manzana

Línea 2 : Acá “E” será reemplazado por el tipo Naranja

Línea 3 : Acá “E” será reemplazado por el tipo Fruta

Línea 4 : Acá “E” será reemplazado por el tipo Object

Generic Method

Es un método que tiene uno o más parámetros tipo.

public static <T> T escogerMejorFruta(T[] frutas){
for (T fruta : frutas) {
boolean esMejorFruta= true;
if (esMejorFruta){
return fruta;
}
}
return null;
}

Acá podemos encontrar el símbolo “<T>” que es la manera como indicamos que es un método genérico y que el tipo es “T”, el cual es un parámetro formal que luego será reemplazado por su valor real. Observar que una vez definido, puede ser usado en cualquier parte del método. A continuación vemos un ejemplo del uso de este método:

Fruta[] grupoDeFrutas = new Fruta[]{new Manzana(), new Naranja()};
Fruta mejorFruta = SelectorDeFrutas.escogerMejorFruta(grupoDeFrutas);

Acá vemos que no necesitamos indicarle el tipo del parámetro, el compilador analizará y en base al tipo enviado como parámetro del método deducirá el valor de T, en este caso  T = Fruta.

También podemos indicar explícitamente el valor de “T” de la siguiente manera:

Fruta[] grupoDeFrutas = new Fruta[]{new Manzana(), new Naranja()};
Fruta mejorFruta = SelectorDeFrutas.<Fruta>escogerMejorFruta(grupoDeFrutas);

A tener en Cuenta

1) Las interfaces también pueden ser genéricas

2) Normalmente la convención para los nombres de los parámetros tipo, es usar una sola letra mayúscula. Por ejemplo:

<E>: Para elementos de una colección:

<T>: Para tipo en general.

<K , V>: Para la clave y el valor de un Map por ejemplo.

<N>: Para números.

S, U, V: Para otros parámetros

Generics

Monday, June 14th, 2010 Autor : gmateo

Código Fuente: Ejemplo 1

Los Generics nos permiten abstraer sobre tipos; en otras palabras, nos dan la libertad para ser genéricos acerca de los tipos al momento de definir las clases y métodos, pero debemos ser específicos durante la invocación o la instanciación.

Para entender mejor esto veamos lo siguiente:

Problema

Se tiene la necesidad de diseñar una caja para colocar en ella frutas.

Solución

Para esto vamos a tener las siguientes clases:

Fruta

Esta clase abstraerá el concepto detrás de toda fruta.

public class Fruta {
}

Manzana

Extiende de la clase Fruta.

public class Manzana extends Fruta{
}

Naranja

Extiende de la clase fruta

public class Naranja extends Fruta{
}

Caja

Abstrae el concepto de caja.

public class Caja<E> {
List frutas = new ArrayList();
public void poner(E fruta){
frutas.add(fruta);
}
}

En esta definición debemos notar: “<E>” con esto indicamos que la clase Caja puede recibir un parámetro tipo. También debemos notar que una vez definido así, podemos usar este tipo dentro de la clase, por ejemplo lo estamos usando como parámetro del método poner.

Teniendo en cuenta lo anterior revisemos los siguientes casos:

Caso 1:

   1: Caja<Manzana> cajaDeManzanas = new Caja<Manzana>();

   2: cajaDeManzanas.poner(new Manzana());

   3: //cajaDeManzanas.poner(new Naranja());

   4: //cajaDeManzanas.poner(new Fruta());

   5: //cajaDeManzanas.poner(new Object());

Líneas a tener en cuenta:

Línea 1: Acá estamos creando una caja de Manzanas.

Línea 2: Acá agregamos una Manzana a la caja.

Línea 3: No podemos agregar Naranjas a la caja

Línea 4: No podemos agregar Frutas a la caja

Línea 5: No podemos agregar Object a la caja

Vemos que una vez definida la caja como caja de Manzanas, sólo puede recibir manzanas. Lo mismo pasará cuando escojamos caja de Naranjas.

Caso 2

   1: Caja<Fruta> cajaDeFrutas = new Caja<Fruta>();

   2: cajaDeFrutas.poner(new Fruta());

   3: cajaDeFrutas.poner(new Manzana());

   4: cajaDeFrutas.poner(new Naranja());

   5: //cajaDeFrutas.poner(new Object());

Líneas a tener en cuenta:

Línea 1: Acá estamos creando una caja de Frutas

Línea 2: Acá agregamos una Fruta a la caja.

Línea 3: Acá agregamos una Manzana a la caja.

Línea 4: Acá agregamos una Naranja a la caja.

Línea 5: No podemos agregar Object a la caja

Vemos que si definimos la caja como una caja de frutas nos permitirá ingresar: Frutas, Naranjas y Manzanas, es decir cualquier objeto que extienda de Fruta.