¿Qué es genéricos en Java?

Generics in Java se introdujo en 2004 como una nueva característica del lenguaje de programación Java y formaba parte de la versión JDK 5. Se usa más ampliamente junto con el marco de colecciones de Java. A partir de hoy es una de las características más destacadas y buscadas del lenguaje de programación Java.

Cuatro individuos encontraron Java genérico, a saber, Gilad Bracha, Martin Odersky, David Stoutamire y Philip Wadler en 1998. Era una extensión del lenguaje Java que admitía tipos genéricos. Estaba destinado a lograr dos objetivos principales que son:

  1. Tipo de seguridad
  2. Código de reutilización

Definición de genéricos en Java

Los genéricos se pueden definir como una forma de lograr la reutilización del código mediante la definición de clases genéricas, interfaces, constructores y métodos que se pueden usar con diferentes tipos de datos y también lograr la seguridad de los tipos al declarar el tipo de datos que se usa en la implementación de antemano, eliminando así Las posibilidades de un error en tiempo de ejecución.

¿Cómo se implementan los genéricos en Java?

Los genéricos se implementan utilizando corchetes angulares "". Los corchetes encierran el parámetro tipo "T" dentro de ellos. Ejemplo El parámetro de tipo "T" es un marcador de posición que indica que se le asignará un tipo de datos en tiempo de ejecución. Por ejemplo, una clase genérica se definirá como:

public class MyGenericClass (…)

Los siguientes son los parámetros de tipo estándar:

  • T: tipo
  • E: elemento
  • N: número
  • K: clave
  • V: valor

S, U, V y así sucesivamente se utilizan para definir el segundo, tercer y cuarto parámetros respectivamente en caso de que se usen parámetros múltiples.

Comprender los genéricos en Java

A estas alturas, es posible que se pregunte qué es la seguridad de tipos y cómo funciona. ¿O en qué se diferencian las clases, interfaces, constructores y métodos genéricos de nuestras clases y métodos regulares que los hacen reutilizables? Vamos a averiguar.

Java es un lenguaje de tipo estático que requiere que declare el "tipo" que es el tipo de datos del valor retenido por la variable antes de usarlo.

Ejemplo: String myString =”eduCBA”;

Aquí "String" es el tipo de datos, "myString" es la variable que contendrá un valor cuyo tipo es String.

Ahora, si intenta pasar un valor booleano en lugar de una cadena, por ejemplo:

String myBooleanStr = true;

Inmediatamente obtendrá un error en tiempo de compilación que indica "No coinciden los tipos: no se puede convertir de booleano a String".

¿Cómo logramos la reutilización del código con genéricos?

Ahora, definamos un método regular:

public static void welcome(String name)(
System.out.println("welcome to " + name);
)

Este método solo se puede invocar pasando un parámetro de cadena. Por ejemplo:

welcome(“eduCBA”);

Su salida será "bienvenido a eduCBA".

Sin embargo, no puede invocar este método sin pasar por ningún otro tipo de datos, como entero o booleano. Si intenta hacer eso, se le pedirá un error en tiempo de compilación que indique "El método welcome (String) en el tipo Runner no es aplicable para los argumentos (boolean)". Lo que significa que no puede pasar ningún otro tipo de datos a un método que solo acepte una cadena como parámetro.

Esto también significa que si desea invocar un método similar para un tipo de datos diferente, tendrá que escribir un nuevo método que acepte el tipo de datos requerido como parámetro. Esta característica de reescribir métodos con parámetros de diferentes tipos de datos también se conoce como sobrecarga de métodos. El principal inconveniente de esto es que aumenta el tamaño de su código.

Sin embargo, también podríamos usar Generics para volver a escribir el método anterior y usarlo para cualquier tipo de datos que necesitemos.

Definiendo un método genérico:

public static void welcome(T t)(
System.out.println("it is " + t);
)

Nota : Aquí "t" es un objeto del tipo T. A T se le asignará el tipo de datos que se está utilizando para invocar el método.

Ahora puede reutilizar este método invocandolo para una cadena cuando sea necesario o un booleano o un entero o cualquier otro tipo de datos.

welcome("educate");
Integer Myint = 1;
welcome(Myint)
welcome(true);

Las declaraciones anteriores proporcionarán el siguiente resultado:

Es educa
Es 1
Eso es verdad

Por lo tanto, al usar genéricos aquí, podemos reutilizar nuestro método para diferentes tipos de datos.

¿Cómo logramos la seguridad de tipos usando genéricos?

Una de las principales diferencias entre las matrices y la colección es que las matrices solo pueden almacenar datos homogéneos, mientras que las colecciones pueden almacenar datos heterogéneos. Es decir, Colecciones puede almacenar cualquier tipo de datos / objetos definidos por el usuario.

NOTA: Las colecciones solo pueden contener objetos (tipo de datos definidos por el usuario) y no un tipo de datos primitivo. Para trabajar con datos primitivos, las colecciones de tipos utilizan clases de contenedor.

Ahora, consideremos una ArrayList.

ArrayList myList = new ArrayList();

Agreguemos datos de tipo String, Integer y Double al objeto ArrayList.

myList.add("eduCBA");
myList.add(1);
myList.add(5.2);

Al imprimir el objeto ArrayList podemos ver que contiene los siguientes valores: (eduCBA, 1, 5.2).

Ahora, si desea recuperar estos valores en variables, deberá escribirlos a máquina.

String someStr = (String)myList.get(0);
Integer someInt = (Integer)myList.get(1);
Double someFlt = (Double)myList.get(2);

En caso de que no escriba, se le indicará un error en tiempo de compilación que indica "No coinciden los tipos: no se puede convertir de Objeto a Cadena".

A partir de esto, puede concluir que mientras recupera los objetos de su ArrayList, necesita convertirlo a sus respectivos tipos. La pregunta que surge aquí es ¿cómo sabrá a qué tipo de datos se debe encasillar? En tiempo real, su ArrayList contendrá miles de registros y su conversión a tipos de datos diferentes para cada objeto individual no será una opción. Puede terminar convirtiéndolo en el tipo de datos incorrecto. ¿Qué pasa entonces?

Esta vez no obtendrá un error de tiempo de compilación, pero arrojará un error de tiempo de ejecución que indica "Excepción en el hilo" principal "java.lang.ClassCastException: java.lang.Integer no se puede convertir a java.lang.String en com.serviceClasess.Runner .main (Runner.java:43) ".

Como no podemos garantizar el tipo de datos presentes dentro de una colección (en este caso, ArrayList), se considera que no son seguros de usar con respecto al tipo. Aquí es donde entran en juego los genéricos para proporcionar seguridad de tipo.

Usando ArrayList con genéricos:

ArrayList myList = new ArrayList();

Observe que dentro de los corchetes angulares "", se especifica el tipo de cadena, lo que significa que esta implementación particular de ArrayList solo puede contener datos de tipo cadena. Si intenta agregar cualquier otro tipo de datos, simplemente arrojará un error de tiempo de compilación. Aquí ha hecho que su ArrayList sea de tipo seguro al eliminar la posibilidad de agregar un tipo de datos diferente que no sea "String".

Ahora que ha especificado el tipo de datos que se puede agregar a su colección con la ayuda de genéricos, ya no necesita escribirlos al recuperar sus datos. Es decir, simplemente puede recuperar sus datos escribiendo:

String someStr = myList.get(0);

¿Cómo hace Generics en Java que trabajar sea tan fácil?

Ayuda a que sus colecciones sean seguras para los tipos, lo que garantiza que su código no falle en un momento posterior debido a una excepción de tiempo de ejecución. También evita que el codificador tenga que escribir todos los objetos de la colección, lo que hace que el desarrollo del código sea más rápido y fácil. Al hacer uso de clases y métodos genéricos, también se puede reutilizar el código según el tipo de datos requerido durante la implementación.

¿Qué más puedes hacer con Generics en Java?

Hasta ahora hemos visto cómo podemos lograr la seguridad del tipo y la reutilización del código con genéricos. Ahora veamos las otras características que proporcionan los genéricos. Son:

  1. Tipos acotados y múltiples acotados
  2. Escriba comodines

Tipo acotado : en el caso de un tipo acotado, el tipo de datos de un parámetro se acota a un rango particular. Esto se logra con la ayuda de la palabra clave "extiende".

Por ejemplo, consideremos una clase genérica con un parámetro de tipo acotado que extiende la interfaz Runnable:

class myGenericClass()

Ahora, mientras crea su objeto en otra clase:

myGenericClass myGen = new myGenericClass();

La declaración anterior se ejecutará perfectamente sin ningún error. Es decir, en el caso del tipo acotado, puede pasar el mismo tipo de clase o su tipo de clase secundaria. Además, puede vincular el tipo de parámetro a una interfaz y pasar sus implementaciones al invocarlo, como en el caso de nuestro ejemplo anterior.

¿Qué sucede si intentas usar cualquier otro tipo de parámetro?

myGenericClass myGen = new myGenericClass();

En el caso anterior, obtendrá un error en tiempo de compilación que indica "No coinciden los límites: el tipo Integer no es un sustituto válido para el tipo de letra del tipo myGenericClass".

Tipos acotados múltiples: en el caso de los tipos acotados múltiples, podemos vincular el tipo de datos del parámetro a más de un tipo. Por ejemplo,

Class myGeneric()

En este caso, puede pasar cualquier tipo que amplíe la clase Number e implemente la interfaz Runnable. Sin embargo, cuando se usan varios tipos acotados, se deben tener en cuenta algunas cosas:

  1. No podemos extender más de una clase a la vez.
  2. Podemos extender cualquier número de interfaces a la vez si no hay límite para las interfaces.
  3. El nombre de la clase siempre debe venir primero seguido del nombre de la interfaz, de lo contrario, se producirá un error en tiempo de compilación.

Escriba comodines: están representados por “?”: Símbolo de signo de interrogación. Utiliza dos palabras clave principales:

se extiende (para definir el límite superior) y super (para definir los límites inferiores).

Por ejemplo,

ArrayList al

Este objeto ArrayList "al" contendrá cualquier dato de tipo T y todas sus subclases.

ArrayList al

Este objeto ArrayList "al" contendrá cualquier dato de tipo T y todas sus superclases.

Ventajas de los genéricos en Java

1. Flexibilidad : Generics proporciona a nuestro código la flexibilidad para acomodar diferentes tipos de datos con la ayuda de clases y métodos genéricos.

2. Mantenimiento y reutilización del código : debido a clases y métodos genéricos, no es necesario volver a escribir el código, en caso de un cambio en los requisitos en una etapa posterior que haga que el código sea más fácil de mantener y reutilizar.

3. Seguridad de tipo: proporciona seguridad de tipo al marco de recopilación definiendo el tipo de datos que la recopilación puede contener de antemano y eliminando cualquier posibilidad de falla en el tiempo de ejecución debido a ClassCastException.

4. Eliminación de la necesidad de escribir a máquina: dado que los tipos de datos que contienen las colecciones ya están determinados, no es necesario escribirlos al momento de la recuperación. Esto reduce la longitud del código y también reduce el esfuerzo de un codificador.

Genéricos en habilidades Java

Para trabajar con genéricos, debe estar bien versado en los conceptos básicos de Java. Debe comprender cómo funciona la verificación de tipos y la conversión de tipos. Es necesario un conocimiento profundo de otros conceptos, como la sobrecarga de métodos, la relación entre las clases principales y secundarias, las interfaces y sus implementaciones. También es crucial comprender la diferencia entre los tipos de datos primitivos (tipo de datos definidos por el sistema) y los objetos (tipo de datos definidos por el usuario) cuando se trata de trabajar con el marco de recopilación.

¿Por qué deberíamos usar Generics en Java?

El uso de genéricos hace que nuestro código sea más fácil de mantener, ya que reduce la necesidad de reescribir el código específico del tipo de datos cada vez que hay un cambio en los requisitos. Al usar el tipo limitado genérico, podría restringir el tipo de datos y al mismo tiempo proporcionar flexibilidad a su código al definir su rango. Es menos probable que su código falle más adelante, ya que proporciona seguridad de tipo y hace que su código sea menos propenso a errores.

Alcance de los genéricos en Java

El alcance genérico se limita al tiempo de compilación. Eso significa que el concepto genérico es aplicable solo en tiempo de compilación pero no en tiempo de ejecución. Por ejemplo,

ArrayList myList = new ArrayList();

ArrayList myList = new ArrayList();

ArrayList myList = new ArrayList();

ArrayList myList = new ArrayList();

Aquí todas las cuatro declaraciones anteriores son una y la misma. Permitirán la adición de cualquier tipo de datos al objeto de la lista.

Conclusión

Generics hace que la codificación sea fácil para un codificador. Disminuye las posibilidades de encontrar ClassCastException en tiempo de ejecución al proporcionar una fuerte verificación de tipos. Elimina por completo la necesidad de la conversión de tipos, lo que significa que se debe escribir menos código. Nos proporciona la posibilidad de desarrollar algoritmos genéricos que son independientes del tipo de datos con el que están trabajando.

Artículos recomendados

Esta ha sido una guía de ¿Qué son los genéricos en Java? Aquí discutimos las habilidades, el alcance, el trabajo, la comprensión y la ventaja de los genéricos en Java. También puede consultar nuestros otros artículos sugeridos para obtener más información:

  1. ¿Qué es la interfaz de puerta de enlace común?
  2. Cómo instalar Java 8
  3. ¿Qué es el jabón?
  4. ¿Qué es JavaScript?
  5. Booleanos de Java