Copiado de La web del Programador
Una idea aproximada de las clases y métodos genéricos es pensar que reciben, además de los parámetros normales, "parámetros de tipo".
Por poner el típico ejemplo:
Imagina que quieres una lista que guarde sólo objetos de tipo Usuario, para garantizar que no se mezclan con otros objetos. Podrías hacer algo como:
Pero tú no quieres una única lista en la que pueda haber cualquier cosa. Tú quieres que en una lista determinada sólo pueda haber objetos de un tipo. Si pudieras pasarle al constructor de la clase ese tipo como parámetro... que le pudieras decir "new ListaDe(Usuario)" y "new ListaDe(Cuenta)"... eso estaría bien, no?
Pues exactamente eso es lo que ofrecen los generics. Puedes hacer algo como:
Esto es sólo el principio y de forma muy breve. Los generics permiten hacer cosas más elaboradas.
El siguiente pedazo de código es un ejemplo típico de cómo se utilizan las colecciones en java
Si al código anterior hubiéramos metido una línea extra como la siguiente:
Hubiera dado un error en tiempo de ejecución en concreto una excepción de tipo java.lang.ClassCastException.
Pero bueno veamos la sintaxis de cómo se declara un tipo genérico en java:
De esta manera estaremos le estaremos diciendo al compilador que estaremos creando una lista que almacena puros String en caso de que tratemos de almacenar otro tipo de objeto no dará un error en tiempo de compilación.
Los genéricos ayudaron a crear una comprobación de tipo en listas y mapas en tiempo de compilación, anteriormente esto no era posible y en grandes proyectos el programador tenía que estar recordando que es lo que había metido en cada list o map haciendo difícil el encontrar los errores de programación.
Otra cosa que podemos hacer en con los tipos parametrizados o genéricos es crear nuestras propias clases. Por ejemplo:
Supongamos que tenemos la siguiente clase
De nuevo tenemos el mismo problema, si a la hora de obtener un objeto se requeriría un casting y y si tenemos un casting de distintos tipos no arrojaría un error en tiempo de ejecución. Pero si tenemos una clase como la siguiente:
Veremos que hemos reemplazado los object por un tipo parametrizado. Esto lo podemos pensar que cuando instanciemos la clase:
Todos los tipos parametrizados se reemplazaran por la Clase que está entre los corchetes angulares.
Convenciones
Por convención los tipos parametrizados son letras solitarias mayúsculas, sin esta convención sería difícil leer el código y decir la diferencia entre una variable parametrizada y una clase ordinaria o un nombre de una interface.
E – Elemento (Usado extensivamente en las colecciones en java)
K – Key
N – Number
T – Type
V – Value
S, U, V etc. – 2nd, 3rd, 4th types
Tipos Genéricos en métodos
Veamos el siguiente código
Como vemos hemos añadido un método genérico llamado inspeccion que define un tipo parametrizado llamado U, por defecto este método acepta un objeto de cualquier tipo y lo imprime en pantalla.
Pero habrá algunas ocasiones en que queramos limitar los tipos parametrizados, por ejemplo una tendremos un método que acepte solamente instancias de Number o sus subclases.
Para declarar un tipo genérico limitado usamos la palabra extends seguida de la clase sobre la que queremos limitar. Por ejemplo:
Para especificar interfaces adicionales se debe usar el carácter &. Por ejemplo:
Antes de Java 5 cuando introducíamos objetos en una colección estos se guardaban como objetos de tipo
Esta forma de trabajar no solo nos ocasiona tener que escribir más código innecesariamente, sino que es propenso a errores porque carecemos de un sistema de comprobación de tipos. Si introdujeramos un objeto de tipo incorrecto el programa compilaría pero lanzaría una excepción en tiempo de ejecución al intentar convertir el objeto en
Podríamos utilizar el operador
Desde Java 5 contamos con una característica llamada generics que puede solventar esta clase de problemas. Los generics son una mejora al sistema de tipos que nos permite programar abstrayéndonos de los tipos de datos, de forma parecida a los templates o plantillas de C++ (pero mejor).
Gracias a los generics podemos especificar el tipo de objeto que introduciremos en la colección, de forma que el compilador conozca el tipo de objeto que vamos a utilizar, evitándonos así el casting. Además, gracias a esta información, el compilador podrá comprobar el tipo de los objetos que introducimos, y lanzar un error en tiempo de compilación si se intenta introducir un objeto de un tipo incompatible, en lugar de que se produzca una excepción en tiempo de ejecución.
Para utilizar generics con nuestras colecciones tan solo tenemos que indicar el tipo entre < y > a la hora de crearla. A estas clases a las que podemos pasar un tipo como “parámetro” se les llama clases parametrizadas, clases genéricas, definiciones genéricas o simplemente genéricas (generics). Veamos un ejemplo con la colección List.
Este código, sin embargo, no compilaría, dado que hemos indicado que nuestra colección contendrá objetos de tipo
Algo a tener en cuenta es que el tipo parámetro debe ser una clase; no podemos utilizar tipos primitivos. Esto, sin embargo, no es ningún problema gracias a las características de autoboxing y unboxing de Java 5. En el código siguiente, por ejemplo, se crea un objeto
Ahora, ¿cómo podemos crear nuestras propias clases parametrizadas? ¿cómo funciona a partir de Java 5 una colección como
Como ejemplo vamos a crear una pequeña clase que almacene un objeto, con su getter y setter, y que permita imprimir la cadena que lo representa, o bien imprimir la cadena al revés:
Ahora podríamos utilizar nuestra nueva clase de la misma forma que hacíamos con
En ocasiones también puede interesarnos limitar los tipos con los que se puede parametrizar nuestra clase. Por ejemplo, podríamos querer crear una clase con un método que imprimiera el resultado de dividir un número entre 2. No tendría sentido permitir que se pudiera parametrizar la clase con el tipo
Ahora, en Java no existe la herencia múltiple, pero lo que si podemos hacer es implementar varias interfaces distintas. Si quisieramos obligar que el tipo implemente varias interfaces distintas, o que extienda una clase e implemente una o varias interfaces, tendríamos que separar estos tipos con el caracter &:
Otra cosa que podemos hacer es utilizar más de un parámetro de tipo, separando los tipos con comas. Supongamos por ejemplo que quisieramos crear una clase que imprimiera la suma de dos números. Escribiríamos algo como esto:
Por supuesto también podemos crear clases abstractas e interfaces de forma similar a las clases genéricas:
E incluso métodos, en cuyo caso se indica el nombre a utilizar para el tipo genérico antes del valor de retorno del método. Sustituyamos como ejemplo la clase que imprimía las cadenas al revés por un método estático:
A la hora de llamar al método podemos especificar el tipo de la misma forma que hacíamos con las clases genéricas, aunque en este caso es menos útil:
También podemos omitirlo, por supuesto:
Una idea aproximada de las clases y métodos genéricos es pensar que reciben, además de los parámetros normales, "parámetros de tipo".
Por poner el típico ejemplo:
Imagina que quieres una lista que guarde sólo objetos de tipo Usuario, para garantizar que no se mezclan con otros objetos. Podrías hacer algo como:
class ListaDeUsuarios { public void anadir(Usuario usuario) { // guardar un Usuario } public Usuario leer(int i) { //devolver el usuario i } }Imagina ahora que quieres otra lista que sólo pueda guardar objetos de tipo Cuenta. Harías...
class ListaDeCuentas { public void anadir(Cuenta cuenta) { // guardar una Cuenta } public Cuenta leer(int i) { // devolver el usuario i } }Y ahora otra que pueda guardar sólo... etc, etc. Al cabo del rato resulta que tienes un montón de listas muy parecidas, que en lo único que se diferencian es en el tipo de objetos que manejan. Es decir, un montón de código casi repetido.
Pero tú no quieres una única lista en la que pueda haber cualquier cosa. Tú quieres que en una lista determinada sólo pueda haber objetos de un tipo. Si pudieras pasarle al constructor de la clase ese tipo como parámetro... que le pudieras decir "new ListaDe(Usuario)" y "new ListaDe(Cuenta)"... eso estaría bien, no?
Pues exactamente eso es lo que ofrecen los generics. Puedes hacer algo como:
class ListaDe<Tipo> { public void anadir(<Tipo> elemento) { //guardar un elemento } public <Tipo> leer(int i) { //devolver el usuario i } }Ahora podrías decir "new ListaDe<Usuario>()" y en esa lista sólo puedes hacer anadir(usuario), o "new ListaDe<Cuenta>()" y en esa sólo puedes añadir objetos de tipo Cuenta.
Esto es sólo el principio y de forma muy breve. Los generics permiten hacer cosas más elaboradas.
Tipos Genericos en Java
21 02 2010 Los tipos genéricos o también llamados parametrizados en java se introdujeron en la versión de java 1.5, y ayudaron a resolver una gran carencia cuando se utilizan las colecciones en java. Por ejemplo:El siguiente pedazo de código es un ejemplo típico de cómo se utilizan las colecciones en java
List objetos = new ArrayList();
objetos.add(new String("hola"));
objetos.add(new String("mundo"));
for(int i = 0; i < objetos.size(); i++)
{
String temp = (String) objetos.get (i);
System.out.println (temp);
}
Si al código anterior hubiéramos metido una línea extra como la siguiente:
objetos.add (new Integer(12));
Hubiera dado un error en tiempo de ejecución en concreto una excepción de tipo java.lang.ClassCastException.
Pero bueno veamos la sintaxis de cómo se declara un tipo genérico en java:
List <String> conjuntoCadenas = new ArrayList <String>();
De esta manera estaremos le estaremos diciendo al compilador que estaremos creando una lista que almacena puros String en caso de que tratemos de almacenar otro tipo de objeto no dará un error en tiempo de compilación.
Los genéricos ayudaron a crear una comprobación de tipo en listas y mapas en tiempo de compilación, anteriormente esto no era posible y en grandes proyectos el programador tenía que estar recordando que es lo que había metido en cada list o map haciendo difícil el encontrar los errores de programación.
Otra cosa que podemos hacer en con los tipos parametrizados o genéricos es crear nuestras propias clases. Por ejemplo:
Supongamos que tenemos la siguiente clase
public class Box
{
private Object object;
public void add(Object object)
{
this.object = object;
}
public object get()
{
return this.object;
}
}
De nuevo tenemos el mismo problema, si a la hora de obtener un objeto se requeriría un casting y y si tenemos un casting de distintos tipos no arrojaría un error en tiempo de ejecución. Pero si tenemos una clase como la siguiente:
public class Box<T>
{
private T t;
public void add(T t)
{
this.t = t;
}
public T get()
{
return t;
}
}
Veremos que hemos reemplazado los object por un tipo parametrizado. Esto lo podemos pensar que cuando instanciemos la clase:
Box <String> boxes = new Box<String> ();
Todos los tipos parametrizados se reemplazaran por la Clase que está entre los corchetes angulares.
Convenciones
Por convención los tipos parametrizados son letras solitarias mayúsculas, sin esta convención sería difícil leer el código y decir la diferencia entre una variable parametrizada y una clase ordinaria o un nombre de una interface.
E – Elemento (Usado extensivamente en las colecciones en java)
K – Key
N – Number
T – Type
V – Value
S, U, V etc. – 2nd, 3rd, 4th types
Tipos Genéricos en métodos
Veamos el siguiente código
public class Box<T>
{
private T t;
public void add(T t) {
this.t = t;
}
public T get()
{
return t;
}
public <U> void inspeccion (U u)
{
System.out.println("T: " + t.getClass().getName());
System.out.println("U: " + u.getClass().getName());
}
public static void main(String[] args) {
Box<Integer> integerBox = new Box<Integer>();
integerBox.add(new Integer(10));
integerBox.inspeccion("una cadena de texto");
}
}
Como vemos hemos añadido un método genérico llamado inspeccion que define un tipo parametrizado llamado U, por defecto este método acepta un objeto de cualquier tipo y lo imprime en pantalla.
Pero habrá algunas ocasiones en que queramos limitar los tipos parametrizados, por ejemplo una tendremos un método que acepte solamente instancias de Number o sus subclases.
Para declarar un tipo genérico limitado usamos la palabra extends seguida de la clase sobre la que queremos limitar. Por ejemplo:
public <U extends Number> void inspect(U u)
{
System.out.println("T: " + t.getClass().getName());
System.out.println("U: " + u.getClass().getName());
}
public static void main(String[] args)
{
Box<Integer> integerBox = new Box<Integer>();
integerBox.add(new Integer(10));
integerBox.inspect("some text"); //Marcara error en tiempo de compilación
}
Para especificar interfaces adicionales se debe usar el carácter &. Por ejemplo:
<U extends Number & MyInterface>
Genericos
Antes de Java 5 cuando introducíamos objetos en una colección estos se guardaban como objetos de tipo
Object
, aprovechando el polimorfismo para poder introducir cualquier tipo de objeto en la colección. Esto nos obligaba a hacer un casting al tipo original al obtener los elementos de la colección.- import java.util.ArrayList;
- import java.util.List;
- public class Ejemplo {
- public static void main(String[] args) {
- List lista = new ArrayList();
- lista.add("Hola mundo");
- String cadena = (String) lista.get(0);
- System.out.println(cadena);
- }
- }
String
:- import java.util.ArrayList;
- import java.util.List;
- public class Ejemplo {
- public static void main(String[] args) {
- List lista = new ArrayList();
- lista.add(22);
- String cadena = (String) lista.get(0);
- System.out.println(cadena);
- }
- }
instanceof
para comprobar el tipo del objeto antes de hacer el casting o antes de introducirlo en la colección, pero es poco elegante, añade aún más código a escribir, y no nos evitaría tener que hacer el casting.Desde Java 5 contamos con una característica llamada generics que puede solventar esta clase de problemas. Los generics son una mejora al sistema de tipos que nos permite programar abstrayéndonos de los tipos de datos, de forma parecida a los templates o plantillas de C++ (pero mejor).
Gracias a los generics podemos especificar el tipo de objeto que introduciremos en la colección, de forma que el compilador conozca el tipo de objeto que vamos a utilizar, evitándonos así el casting. Además, gracias a esta información, el compilador podrá comprobar el tipo de los objetos que introducimos, y lanzar un error en tiempo de compilación si se intenta introducir un objeto de un tipo incompatible, en lugar de que se produzca una excepción en tiempo de ejecución.
Para utilizar generics con nuestras colecciones tan solo tenemos que indicar el tipo entre < y > a la hora de crearla. A estas clases a las que podemos pasar un tipo como “parámetro” se les llama clases parametrizadas, clases genéricas, definiciones genéricas o simplemente genéricas (generics). Veamos un ejemplo con la colección List.
- import java.util.ArrayList;
- import java.util.List;
- public class Ejemplo {
- public static void main(String[] args) {
- List<String> lista = new ArrayList<String>();
- lista.add("Hola mundo");
- String cadena = lista.get(0);
- System.out.println(cadena);
- }
- }
String
, y no Integer
:- import java.util.ArrayList;
- import java.util.List;
- public class Ejemplo {
- public static void main(String[] args) {
- List<String> lista = new ArrayList<String>();
- lista.add(new Integer(22));
- Integer numero = lista.get(0);
- System.out.println(numero);
- }
- }
Integer
intermedio de forma transparente, que es el objeto que se introducirá en realidad en la colección, y no un int
de valor 22, como podría parecer. Posteriormente el objeto Integer
se vuelve a convertir en int
automáticamente.- import java.util.ArrayList;
- import java.util.List;
- public class Ejemplo {
- public static void main(String[] args) {
- List<Integer> lista = new ArrayList<Integer>();
- lista.add(22);
- int numero = lista.get(0);
- System.out.println(numero);
- }
- }
List
? Sencillo. Solo tenemos que añadir el tipo parámetro después del nombre de la clase, y utilizar el nombre de este tipo genérico donde usaríamos un tipo concreto (con algunas excepciones). Por convención se suele utilizar una sola letra mayúscula para el tipo genérico.Como ejemplo vamos a crear una pequeña clase que almacene un objeto, con su getter y setter, y que permita imprimir la cadena que lo representa, o bien imprimir la cadena al revés:
- public class Imprimidor<T> {
- private T objeto;
- public Imprimidor(T objeto) {
- this.objeto = objeto;
- }
- public void setObjeto(T objeto) {
- this.objeto = objeto;
- }
- public T getObjeto() {
- return objeto;
- }
- public void imprimir() {
- System.out.println(objeto.toString());
- }
- public void imprimir_reves() {
- StringBuffer sb = new StringBuffer(objeto.toString());
- System.out.println(sb.reverse());
- }
- }
List
:- public class Ejemplo {
- public static void main(String[] args) {
- Imprimidor<String> impStr = new Imprimidor<String>("Hola mundo");
- impStr.imprimir();
- Imprimidor<Integer> impInt = new Imprimidor<Integer>(143);
- impInt.imprimir_reves();
- }
- }
String
; nos interesaría pues encontrar alguna forma de indicar que el tipo utilizado debe ser un subtipo de la clase Number
. Nuestro código podría tener un aspecto similar al siguiente:- public class Divisor<T extends Number> {
- private T numero;
- public Divisor(T numero) {
- this.numero = numero;
- }
- public void dividir() {
- System.out.println(numero.doubleValue() / 2);
- }
- }
- public class Divisor<T extends A & B> {
- ...
- }
- public class Sumador<T1 extends Number, T2 extends Number> {
- private T1 numero1;
- private T2 numero2;
- public Sumador(T1 numero1, T2 numero2) {
- this.numero1 = numero1;
- this.numero2 = numero2;
- }
- public void sumar() {
- System.out.println(numero1.doubleValue() + numero2.doubleValue());
- }
- }
- public interface MiColeccion<T> {
- public void anyadir(T objeto);
- public T obtener();
- public void ordenar();
- }
- public class EjemploGenerics {
- public static <T> void imprimir_reves(T objeto) {
- StringBuffer sb = new StringBuffer(objeto.toString());
- System.out.println(sb.reverse());
- }
- }
- public class EjemploGenerics {
- public static <T> void imprimir_reves(T objeto) {
- StringBuffer sb = new StringBuffer(objeto.toString());
- System.out.println(sb.reverse());
- }
- public static void main(String args[]) {
- String cadena = new String("Hola");
- EjemploGenerics ej = new EjemploGenerics();
- ej.<string>imprimir_reves(cadena);
- }
- }</string>
- public class EjemploGenerics {
- public static <T> void imprimir_reves(T objeto) {
- StringBuffer sb = new StringBuffer(objeto.toString());
- System.out.println(sb.reverse());
- }
- public static void main(String args[]) {
- String cadena = new String("Hola");
- Integer entero = new Integer(12);
- Float flotante = new Float(13.6);
- Object objeto = new Object();
- imprimir_reves(cadena);
- imprimir_reves(entero);
- imprimir_reves(flotante);
- imprimir_reves(objeto);
- }
- }
No hay comentarios:
Publicar un comentario