CodeGym/Blog Java/Random-ES/Genéricos en Java
Autor
Volodymyr Portianko
Java Engineer at Playtika

Genéricos en Java

Publicado en el grupo Random-ES
¡Hola! Vamos a hablar de Java Generics. ¡Debo decir que aprenderás mucho! No solo esta lección, sino también las próximas lecciones estarán dedicadas a los genéricos. Entonces, si estás interesado en los genéricos, hoy tienes suerte: aprenderás mucho sobre las características de los genéricos. Y si no, resignarse y relajarse! :) Este es un tema muy importante, y necesitas saberlo. Comencemos con lo simple: el "qué" y el "por qué".

¿Qué son los genéricos de Java?

Los genéricos son tipos que tienen un parámetro. Al crear un tipo genérico, especifica no solo un tipo, sino también el tipo de datos con el que funcionará. Supongo que el ejemplo más obvio ya te ha venido a la mente: ¡ArrayList! Así es como solemos crear uno en un programa:
import java.util.ArrayList;
import java.util.List;

public class Main {

   public static void main(String[] args) {

       List<String> myList1 = new ArrayList<>();
       myList1.add("Test String 1");
       myList1.add("Test String 2");
   }
}
Como puede suponer, una característica de esta lista es que no podemos meter todo en ella: funciona exclusivamente con objetos String . Ahora hagamos una pequeña digresión en la historia de Java y tratemos de responder a la pregunta "¿por qué?" Para hacer esto, escribiremos nuestra propia versión simplificada de la clase ArrayList. Nuestra lista solo sabe cómo agregar y recuperar datos de una matriz interna:
public class MyListClass {

   private Object[] data;
   private int count;

   public MyListClass() {
       this.data = new Object[10];
       this.count = 0;
   }

   public void add(Object o) {
       this.data[count] = o;
       count++;
   }

   public Object[] getData() {
       return data;
   }
}
Supongamos que queremos que nuestra lista solo almacene Integer s. No estamos usando un tipo genérico. No queremos incluir una comprobación explícita de "instancia de entero " en el método add() . Si lo hiciéramos, toda nuestra clase sería adecuada solo para Integer , ¡y tendríamos que escribir una clase similar para todos los demás tipos de datos del mundo! Confiaremos en nuestros programadores y dejaremos un comentario en el código para asegurarnos de que no agreguen nada que no queramos:
// Use this class ONLY with the Integer data type
public void add(Object o) {
   this.data[count] = o;
   count++;
}
Uno de los programadores se perdió este comentario y, sin darse cuenta, colocó varias cadenas en una lista de números y luego calculó su suma:
public class Main {

   public static void main(String[] args) {

       MyListClass list = new MyListClass();
       list.add(100);
       list.add(200);
       list.add("Lolkek");
       list.add("Shalala");

       Integer sum1 = (Integer) list.getData()[0] + (Integer) list.getData()[1];
       System.out.println(sum1);

       Integer sum2 = (Integer) list.getData()[2] + (Integer) list.getData()[3];
       System.out.println(sum2);
   }
}
Salida de la consola:
300
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
      at Main.main (Main.java:14)
¿Qué es lo peor de esta situación? Ciertamente no es el descuido del programador. La peor parte es que el código incorrecto terminó en un lugar importante de nuestro programa y se compiló con éxito. Ahora encontraremos el error no mientras escribimos el código, sino solo durante las pruebas (¡y este es el mejor de los casos!). La corrección de errores en etapas posteriores del desarrollo cuesta mucho más, tanto en términos de dinero como de tiempo. Aquí es precisamente donde los genéricos nos benefician: una clase genérica permite que el desafortunado programador detecte el error de inmediato. ¡El programa simplemente no compilará!
import java.util.ArrayList;
import java.util.List;

public class Main {

   public static void main(String[] args) {

       List<Integer> myList1 = new ArrayList<>();

       myList1.add(100);
       myList1.add(100);
       myList1.add ("Lolkek"); // Error!
       myList1.add("Shalala"); // Error!
   }
}
El programador se da cuenta inmediatamente de su error y mejora instantáneamente. Por cierto, no tuvimos que crear nuestra propia clase List para ver este tipo de error. ¡Simplemente elimine los corchetes angulares y escriba ( <Integer> ) de una ArrayList ordinaria!
import java.util.ArrayList;
import java.util.List;

public class Main {

   public static void main(String[] args) {

      List list = new ArrayList();

      list.add(100);
      list.add(200);
      list.add("Lolkek");
      list.add("Shalala");

       System.out.println((Integer) list.get(0) + (Integer) list.get(1));
       System.out.println((Integer) list.get(2) + (Integer) list.get(3));
   }
}
Salida de la consola:
300
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
     at Main.main(Main.java:16)
En otras palabras, incluso usando los mecanismos "nativos" de Java, podemos cometer este tipo de error y crear una colección insegura. Sin embargo, si pegamos este código en un IDE, recibimos una advertencia: "Llamada no verificada para agregar (E) como miembro del tipo sin procesar de java.util.List". Se nos dice que algo puede salir mal al agregar un elemento. a una colección que carece de un tipo genérico. Pero, ¿qué significa la frase "tipo en bruto"? Un tipo sin formato es una clase genérica cuyo tipo se ha eliminado. En otras palabras, List myList1 es un tipo sin procesar . Lo opuesto a un tipo sin procesar es un tipo genérico : una clase genérica con una indicación de los tipos parametrizados . Por ejemplo, List<String> myList1. Podría preguntarse por qué el lenguaje permite el uso de tipos sin procesar . La razón es simple. Los creadores de Java dejaron soporte para tipos sin procesar en el lenguaje para evitar crear problemas de compatibilidad. Cuando se lanzó Java 5.0 (los genéricos aparecieron por primera vez en esta versión), ya se había escrito una gran cantidad de código utilizando tipos sin procesar . Como resultado, este mecanismo todavía se admite en la actualidad. Hemos mencionado repetidamente el libro clásico de Joshua Bloch "Effective Java" en las lecciones. Como uno de los creadores del lenguaje, no se saltó los tipos sin procesar ni los tipos genéricos en su libro. ¿Qué son los genéricos en Java?  - 2El capítulo 23 del libro tiene un título muy elocuente: "No use tipos sin procesar en código nuevo". Esto es lo que debe recordar. Cuando utilice clases genéricas, nunca convierta un tipo genérico en un tipo sin procesar .

Métodos genéricos

Java le permite parametrizar métodos individuales creando los llamados métodos genéricos. ¿Cómo son útiles estos métodos? Sobre todo, son útiles porque le permiten trabajar con diferentes tipos de parámetros de métodos. Si la misma lógica se puede aplicar de forma segura a diferentes tipos, un método genérico puede ser una gran solución. Considere este como un ejemplo muy simple: Supongamos que tenemos una lista llamada myList1 . Queremos eliminar todos los valores de la lista y llenar todos los espacios vacíos con nuevos valores. Así es como se ve nuestra clase con un método genérico:
public class TestClass {

   public static <T> void fill(List<T> list, T val) {
       for (int i = 0; i < list.size(); i++)
           list.set(i, val);
   }

   public static void main(String[] args) {

       List<String> strings = new ArrayList<>();
       strings.add("Old String 1");
       strings.add("Old String 2");
       strings.add("Old String 3");

       fill(strings, "New String");

       System.out.println(strings);

       List<Integer> numbers = new ArrayList<>();
       numbers.add(1);
       numbers.add(2);
       numbers.add(3);

       fill(numbers, 888);
       System.out.println(numbers);
   }
}
Presta atención a la sintaxis. Se ve un poco inusual:
public static <T> void fill(List<T> list, T val)
Escribimos <T> antes del tipo de retorno. Esto indica que estamos tratando con un método genérico. En este caso, el método acepta 2 parámetros como entrada: una lista de objetos T y otro objeto T separado. Al usar <T>, parametrizamos los tipos de parámetros del método: no podemos pasar una lista de cadenas y un entero. Una lista de cadenas y una cadena, una lista de enteros y un entero, una lista de nuestros propios objetos Cat y otro objeto Cat : eso es lo que debemos hacer. El método main() ilustra cómo el método fill() se puede usar fácilmente para trabajar con diferentes tipos de datos. Primero, usamos el método con una lista de cadenas y una cadena como entrada, y luego con una lista de enteros y un entero. Salida de la consola:
[New String, New String, New String] [888, 888, 888]
Imagínese si no tuviéramos métodos genéricos y necesitáramos la lógica del método fill() para 30 clases diferentes. ¡Tendríamos que escribir el mismo método 30 veces para diferentes tipos de datos! ¡Pero gracias a los métodos genéricos, podemos reutilizar nuestro código! :)

Clases genéricas

No está limitado a las clases genéricas proporcionadas en las bibliotecas estándar de Java: ¡puede crear las suyas propias! Aquí hay un ejemplo simple:
public class Box<T> {

   private T t;

   public void set(T t) {
       this.t = t;
   }

   public T get() {
       return t;
   }

   public static void main(String[] args) {

       Box<String> stringBox = new Box<>();

       stringBox.set("Old String");
       System.out.println(stringBox.get());
       stringBox.set("New String");

       System.out.println(stringBox.get());

       stringBox.set(12345); // Compilation error!
   }
}
Nuestra clase Box<T> es una clase genérica. Una vez que asignamos un tipo de datos ( <T> ) durante la creación, ya no podemos colocar objetos de otros tipos en él. Esto se puede ver en el ejemplo. Al crear nuestro objeto, indicamos que funcionaría con Strings:
Box<String> stringBox = new Box<>();
Y en la última línea de código, cuando intentamos poner el número 12345 dentro del cuadro, ¡obtenemos un error de compilación! ¡Es fácil! ¡Hemos creado nuestra propia clase genérica! :) Con eso, la lección de hoy llega a su fin. ¡Pero no nos despedimos de los genéricos! En las próximas lecciones, hablaremos sobre funciones más avanzadas, ¡así que no te vayas! ) Para reforzar lo que aprendió, le sugerimos que vea una lección en video de nuestro Curso de Java
¡El mejor de los éxitos en tus estudios! :)
Comentarios
  • Populares
  • Nuevas
  • Antiguas
Debes iniciar sesión para dejar un comentario
Esta página aún no tiene comentarios