Tutorial de ejemplo de genéricos de Java: método, clase e interfaz genéricos

Los genéricos de Java son una de las características más importantes introducidas en Java 5. Si ha estado trabajando en colecciones de Java y con la versión 5 o superior, estoy seguro de que los ha utilizado. Los genéricos en Java con clases de colección son muy fáciles, pero proporcionan muchas más funciones que simplemente crear el tipo de colección. Intentaremos aprender las características de los genéricos en este artículo. Comprender los genéricos puede resultar confuso a veces si utilizamos palabras de jerga, por lo que intentaré mantenerlo simple y fácil de entender.

A continuación analizaremos temas sobre genéricos en Java.

  1. Genéricos en Java

  2. Clase genérica de Java

  3. Interfaz genérica de Java

  4. Tipo genérico de Java

  5. Método genérico de Java

  6. Parámetros de tipo limitados de genéricos de Java

  7. Genéricos y herencia de Java

  8. Clases genéricas y subtipos de Java

  9. Caracteres comodín genéricos de Java

  10. Comodín con límite superior genérico de Java

  11. Comodín ilimitado de genéricos de Java

  12. Comodín de límite inferior genérico de Java

  13. Subtipificación mediante comodines genéricos

  14. Eliminación de tipos genéricos de Java

  15. Preguntas frecuentes sobre medicamentos genéricos

1. Genéricos en Java

Los genéricos se agregaron en Java 5 para proporcionar verificación de tipos en tiempo de compilación y eliminar el riesgo de ClassCastExceptionque esto sucediera con frecuencia al trabajar con clases de colección. Todo el marco de trabajo de colección se reescribió para usar genéricos para la seguridad de tipos. Veamos cómo los genéricos nos ayudan a usar clases de colección de manera segura.

List list = new ArrayList();list.add("abc");list.add(new Integer(5)); //OKfor(Object obj : list){//type casting leading to ClassCastException at runtime    String str=(String) obj; }

El código anterior se compila bien, pero genera una excepción ClassCastException en tiempo de ejecución porque estamos intentando convertir un objeto de la lista en una cadena, mientras que uno de los elementos es de tipo entero. Después de Java 5, usamos clases de colección como las que se muestran a continuación.

ListString list1 = new ArrayListString(); // java 7 ? ListString list1 = new ArrayList(); list1.add("abc");//list1.add(new Integer(5)); //compiler errorfor(String str : list1){     //no type casting needed, avoids ClassCastException}

Tenga en cuenta que en el momento de la creación de la lista, hemos especificado que el tipo de elementos de la lista será String. Por lo tanto, si intentamos agregar cualquier otro tipo de objeto a la lista, el programa generará un error en tiempo de compilación. Observe también que en el bucle for, no necesitamos convertir el tipo de elemento en la lista, por lo que eliminamos la ClassCastException en tiempo de ejecución.

2. Clase genérica de Java

Podemos definir nuestras propias clases con tipos genéricos. Un tipo genérico es una clase o interfaz que está parametrizada sobre tipos. Usamos corchetes angulares () para especificar el parámetro de tipo. Para entender el beneficio, supongamos que tenemos una clase simple como:

package com.journaldev.generics;public class GenericsTypeOld {private Object t;public Object get() {return t;}public void set(Object t) {this.t = t;}        public static void main(String args[]){GenericsTypeOld type = new GenericsTypeOld();type.set("Pankaj"); String str = (String) type.get(); //type casting, error prone and can cause ClassCastException}}

Tenga en cuenta que, al utilizar esta clase, debemos utilizar la conversión de tipos y puede producir ClassCastException en tiempo de ejecución. Ahora, utilizaremos la clase genérica de Java para reescribir la misma clase, como se muestra a continuación.

package com.journaldev.generics;public class GenericsTypeT {private T t;public T get(){return this.t;}public void set(T t1){this.t=t1;}public static void main(String args[]){GenericsTypeString type = new GenericsType();type.set("Pankaj"); //validGenericsType type1 = new GenericsType(); //raw typetype1.set("Pankaj"); //validtype1.set(10); //valid and autoboxing support}}

Observe el uso de la clase GenericsType en el método principal. No necesitamos realizar una conversión de tipos y podemos eliminar ClassCastException en tiempo de ejecución. Si no proporcionamos el tipo en el momento de la creación, el compilador generará una advertencia que indica que “GenericsType es un tipo sin formato. Las referencias al tipo genérico GenericsTypeT deben parametrizarse”. Cuando no proporcionamos el tipo, el tipo se convierte en Objecty, por lo tanto, permite objetos String y Integer. Pero siempre debemos tratar de evitar esto porque tendremos que usar la conversión de tipos mientras trabajamos con un tipo sin formato que puede producir errores en tiempo de ejecución.

Consejo : podemos usar @SuppressWarnings("rawtypes")anotaciones para suprimir la advertencia del compilador, consulte el tutorial de anotaciones de Java .

Tenga en cuenta también que admite el boxing automático de Java .

3. Interfaz genérica de Java

La interfaz comparable es un gran ejemplo de genéricos en interfaces y se escribe así:

package java.lang;import java.util.*;public interface ComparableT {    public int compareTo(T o);}

De manera similar, podemos crear interfaces genéricas en Java. También podemos tener parámetros de varios tipos como en la interfaz Map. Nuevamente, podemos proporcionar un valor parametrizado a un tipo parametrizado, por ejemplo, new HashMapString, ListString();es válido.

4. Tipo genérico de Java

La convención de nomenclatura de tipos genéricos de Java nos ayuda a comprender el código fácilmente y tener una convención de nomenclatura es una de las mejores prácticas del lenguaje de programación Java. Por lo tanto, los genéricos también vienen con sus propias convenciones de nomenclatura. Por lo general, los nombres de los parámetros de tipo son letras mayúsculas simples para que se puedan distinguir fácilmente de las variables de Java. Los nombres de parámetros de tipo más utilizados son:

  • E – Elemento (utilizado ampliamente por el marco de colecciones de Java, por ejemplo ArrayList, Set, etc.)
  • K – Tecla (Usada en el Mapa)
  • N – Número
  • T – Tipo
  • V – Valor (Usado en el Mapa)
  • S,U,V, etc. – 2º, 3º, 4º tipo

5. Método genérico de Java

A veces no queremos que toda la clase esté parametrizada, en ese caso, podemos crear un método genérico de Java. Dado que el constructor es un tipo especial de método, también podemos usar el tipo genérico en los constructores. Aquí hay una clase que muestra un ejemplo de un método genérico de Java.

package com.journaldev.generics;public class GenericsMethods {//Java Generic Methodpublic static T boolean isEqual(GenericsTypeT g1, GenericsTypeT g2){return g1.get().equals(g2.get());}public static void main(String args[]){GenericsTypeString g1 = new GenericsType();g1.set("Pankaj");GenericsTypeString g2 = new GenericsType();g2.set("Pankaj");boolean isEqual = GenericsMethods.StringisEqual(g1, g2);//above statement can be written simply asisEqual = GenericsMethods.isEqual(g1, g2);//This feature, known as type inference, allows you to invoke a generic method as an ordinary method, without specifying a type between angle brackets.//Compiler will infer the type that is needed}}

Observe la firma del método isEqual que muestra la sintaxis para usar tipos genéricos en los métodos. Además, observe cómo usar estos métodos en nuestro programa Java. Podemos especificar el tipo al llamar a estos métodos o podemos invocarlos como un método normal. El compilador Java es lo suficientemente inteligente como para determinar el tipo de variable que se usará; esta función se llama inferencia de tipos .

6. Parámetros de tipo limitados de genéricos de Java

Supongamos que queremos restringir el tipo de objetos que se pueden utilizar en el tipo parametrizado, por ejemplo, en un método que compara dos objetos y queremos asegurarnos de que los objetos aceptados sean comparables. Para declarar un parámetro de tipo acotado, indique el nombre del parámetro de tipo, seguido de la palabra clave extends, seguida de su límite superior, de forma similar al método siguiente.

public static T extends ComparableT int compare(T t1, T t2){return t1.compareTo(t2);}

La invocación de estos métodos es similar a la de un método sin límites, excepto que si intentamos usar una clase que no sea comparable, se generará un error en tiempo de compilación. Los parámetros de tipo acotado se pueden usar con métodos, así como con clases e interfaces. Java Generics también admite límites múltiples, es decir, T extiende A B C. En este caso, A puede ser una interfaz o una clase. Si A es una clase, entonces B y C deberían ser una interfaz. No podemos tener más de una clase en múltiples límites.

7. Genéricos y herencia de Java

Sabemos que la herencia de Java nos permite asignar una variable A a otra variable B si A es una subclase de B. Por lo tanto, podríamos pensar que cualquier tipo genérico de A puede asignarse a un tipo genérico de B, pero no es así. Veámoslo con un programa sencillo.

package com.journaldev.generics;public class GenericsInheritance {public static void main(String[] args) {String str = "abc";Object obj = new Object();obj=str; // works because String is-a Object, inheritance in javaMyClassString myClass1 = new MyClassString();MyClassObject myClass2 = new MyClassObject();//myClass2=myClass1; // compilation error since MyClassString is not a MyClassObjectobj = myClass1; // MyClassT parent is Object}public static class MyClassT{}}

No se nos permite asignar la variable MyClassString a la variable MyClassObject porque no están relacionadas, de hecho, el padre de MyClassT es Object.

8. Clases genéricas y subtipos de Java

Podemos crear subtipos de una clase o interfaz genérica extendiéndola o implementándola. La relación entre los parámetros de tipo de una clase o interfaz y los parámetros de tipo de otra se determina mediante las cláusulas extends e implements. Por ejemplo, ArrayListE implementa ListE que extiende CollectionE, por lo que ArrayListString es un subtipo de ListString y ListString es un subtipo de CollectionString. La relación de subtipo se conserva siempre que no cambiemos el argumento de tipo; a continuación se muestra un ejemplo de múltiples parámetros de tipo.

interface MyListE,T extends ListE{}

Los subtipos de ListString pueden ser MyListString,Object, MyListString,Integer y así sucesivamente.

9. Caracteres comodín genéricos de Java

El signo de interrogación (?) es el comodín en los genéricos y representa un tipo desconocido. El comodín se puede utilizar como el tipo de un parámetro, campo o variable local y, a veces, como un tipo de retorno. No podemos utilizar comodines al invocar un método genérico o al crear una instancia de una clase genérica. En las siguientes secciones, aprenderemos sobre comodines con límite superior, comodines con límite inferior y captura de comodines.

9.1) Comodín con límite superior genérico de Java

Los comodines con límite superior se utilizan para relajar la restricción sobre el tipo de variable en un método. Supongamos que queremos escribir un método que devuelva la suma de números en la lista, por lo que nuestra implementación será algo como esto.

public static double sum(ListNumber list){double sum = 0;for(Number n : list){sum += n.doubleValue();}return sum;}

Ahora bien, el problema con la implementación anterior es que no funcionará con listas de enteros o dobles porque sabemos que ListInteger y ListDouble no están relacionadas, en este caso es útil un comodín de límite superior. Usamos el comodín genérico con la palabra clave extends y la clase o interfaz de límite superior que nos permitirá pasar argumentos de tipos de límite superior o sus subclases. La implementación anterior se puede modificar como el programa siguiente.

package com.journaldev.generics;import java.util.ArrayList;import java.util.List;public class GenericsWildcards {public static void main(String[] args) {ListInteger ints = new ArrayList();ints.add(3); ints.add(5); ints.add(10);double sum = sum(ints);System.out.println("Sum of ints="+sum);}public static double sum(List? extends Number list){double sum = 0;for(Number n : list){sum += n.doubleValue();}return sum;}}

Es similar a escribir nuestro código en términos de interfaz. En el método anterior, podemos usar todos los métodos de la clase de límite superior Number. Tenga en cuenta que con la lista de límite superior, no se nos permite agregar ningún objeto a la lista excepto null. Si intentamos agregar un elemento a la lista dentro del método de suma, el programa no se compilará.

9.2) Comodín ilimitado genérico de Java

A veces nos encontramos en una situación en la que queremos que nuestro método genérico funcione con todos los tipos. En este caso, se puede utilizar un comodín ilimitado. Es lo mismo que utilizar ? extends Object.

public static void printData(List? list){for(Object obj : list){System.out.print(obj + "::");}}

Podemos proporcionar ListString o ListInteger o cualquier otro tipo de argumento de lista de objetos al método printData . De manera similar a la lista de límite superior, no se nos permite agregar nada a la lista.

9.3) Genéricos de Java comodín de límite inferior

Supongamos que queremos agregar números enteros a una lista de números enteros en un método, podemos mantener el tipo de argumento como ListInteger pero estará vinculado con números enteros mientras que ListNumber y ListObject también pueden contener números enteros, por lo que podemos usar un comodín de límite inferior para lograr esto. Usamos comodines genéricos (?) con palabra clave super y clase de límite inferior para lograr esto. Podemos pasar el límite inferior o cualquier supertipo de límite inferior como argumento, en este caso, el compilador de Java permite agregar tipos de objetos de límite inferior a la lista.

public static void addIntegers(List? super Integer list){list.add(new Integer(50));}

10. Subtipificación mediante comodines genéricos

List? extends Integer intList = new ArrayList();List? extends Number  numList = intList;  // OK. List? extends Integer is a subtype of List? extends Number

11. Borrado de tipos genéricos de Java

Los genéricos en Java se agregaron para proporcionar verificación de tipos en tiempo de compilación y no tienen uso en tiempo de ejecución, por lo que el compilador de Java usa la función de borrado de tipos para eliminar todo el código de verificación de tipos de genéricos en el código de bytes e insertar la conversión de tipos si es necesario. El borrado de tipos garantiza que no se creen nuevas clases para tipos parametrizados; en consecuencia, los genéricos no incurren en sobrecarga de tiempo de ejecución. Por ejemplo, si tenemos una clase genérica como la siguiente;

public class TestT extends ComparableT {    private T data;    private TestT next;    public Test(T d, TestT n) {        this.data = d;        this.next = n;    }    public T getData() { return this.data; }}

El compilador de Java reemplaza el parámetro de tipo limitado T con la primera interfaz limitada, Comparable, como el código a continuación:

public class Test {    private Comparable data;    private Test next;    public Node(Comparable d, Test n) {        this.data = d;        this.next = n;    }    public Comparable getData() { return data; }}

12. Preguntas frecuentes sobre medicamentos genéricos

12.1) ¿Por qué utilizamos genéricos en Java?

Los genéricos proporcionan una sólida verificación de tipos en tiempo de compilación y reducen el riesgo de ClassCastException y la conversión explícita de objetos.

12.2) ¿Qué es T en genéricos?

Usamos T para crear una clase, una interfaz y un método genéricos. La T se reemplaza con el tipo real cuando la usamos.

12.3) ¿Cómo funcionan los genéricos en Java?

El código genérico garantiza la seguridad de los tipos. El compilador utiliza el borrado de tipos para eliminar todos los parámetros de tipo en el momento de la compilación y así reducir la sobrecarga en el momento de la ejecución.

13. Genéricos en Java – Lecturas adicionales

  • Los genéricos no admiten subtipos, por lo que ListNumber numbers = new ArrayListInteger();no se compilarán. Descubra por qué los genéricos no admiten subtipos .
  • No podemos crear una matriz genérica, por lo que ListInteger[] array = new ArrayListInteger[10]no compilaremos. Lea ¿por qué no podemos crear una matriz genérica ?

Eso es todo sobre los genéricos en Java . Los genéricos de Java son un tema muy amplio y requieren mucho tiempo para comprenderlos y usarlos de manera efectiva. Esta publicación es un intento de brindar detalles básicos sobre los genéricos y cómo podemos usarlos para ampliar nuestro programa con seguridad de tipos.

SUSCRÍBETE A NUESTRO BOLETÍN 
No te pierdas de nuestro contenido ni de ninguna de nuestras guías para que puedas avanzar en los juegos que más te gustan.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Scroll al inicio