Conceptos de Genéricos en Java

JavaBeginner
Practicar Ahora

Introducción

Los genéricos en Java permiten escribir código flexible y reutilizable al proporcionar una capa adicional de abstracción. Permiten crear un solo algoritmo para múltiples tipos de objetos. Los genéricos proporcionan seguridad de tipos y evitan errores en tiempo de ejecución. En este laboratorio, entenderemos varios aspectos de los genéricos en Java.

Creando una clase genérica

Podemos crear una clase genérica en Java con la ayuda del operador diamante (<>). En el siguiente bloque de código, crearemos una clase genérica llamada MyGenericClass que puede aceptar cualquier tipo de objeto. También crearemos un objeto del tipo MyGenericClass con tipos de enteros y cadenas como parámetros.

// ~/project/MyGenericClass.java

class MyGenericClass<T> {
    T field1;

    public MyGenericClass(T field1) {
        this.field1 = field1;
    }

    public T getField1() {
        return field1;
    }
}

public class Main {
    public static void main(String[] args) {
        MyGenericClass<Integer> myIntObj = new MyGenericClass<Integer>(100);
        MyGenericClass<String> myStringObj = new MyGenericClass<String>("Hello World");

        System.out.println(myIntObj.getField1());
        System.out.println(myStringObj.getField1());
    }
}

Para ejecutar el código, abra la terminal y navegue hasta el directorio ~/project. Compile el código con el siguiente comando:

javac MyGenericClass.java

Después de una compilación exitosa, ejecute el código con el siguiente comando:

java Main

La salida del código será:

100
Hello World

Creando un método genérico

También podemos crear un método genérico en Java. El método genérico puede trabajar con diferentes tipos de objetos. En el siguiente bloque de código, crearemos un método genérico llamado printArray que puede imprimir cualquier tipo de matriz.

// ~/project/Main.java

public class Main {
    public static <T> void printArray(T[] array) {
        for (T element : array)
            System.out.println(element);
    }

    public static void main(String[] args) {
        Integer[] intArray = {1, 2, 3, 4, 5};
        Double[] doubleArray = {1.1, 2.2, 3.3, 4.4};
        String[] stringArray = {"Hello", "World"};

        printArray(intArray);
        printArray(doubleArray);
        printArray(stringArray);
    }
}

Para ejecutar el código, abra la terminal y navegue hasta el directorio ~/project. Compile el código con el siguiente comando:

javac Main.java

Después de una compilación exitosa, ejecute el código con el siguiente comando:

java Main

La salida del código será:

1
2
3
4
5
1.1
2.2
3.3
4.4
Hello
World

Creando un método genérico con límite

También podemos crear un método genérico con límite. Los genéricos con límite limitan el rango de tipos aceptados por el método. Usamos la palabra clave extends para imponer el límite. En el siguiente bloque de código, crearemos un método genérico con límite llamado printNumbers que puede imprimir los números de objetos del tipo Number y sus subclases.

// ~/project/Main.java

public class Main {
    public static <T extends Number> void printNumbers(T[] numbers) {
        for (T number : numbers)
            System.out.println(number);
    }

    public static void main(String[] args) {
        Integer[] intNumbers = {1, 2, 3, 4, 5};
        Double[] doubleNumbers = {1.1, 2.2, 3.3, 4.4};

        printNumbers(intNumbers);
        printNumbers(doubleNumbers);
    }
}

Para ejecutar el código, abra la terminal y navegue hasta el directorio ~/project. Compile el código con el siguiente comando:

javac Main.java

Después de una compilación exitosa, ejecute el código con el siguiente comando:

java Main

La salida del código será:

1
2
3
4
5
1.1
2.2
3.3
4.4

Comprendiendo la seguridad de tipos en genéricos

Los genéricos proporcionan seguridad de tipos, lo que garantiza que definamos solo un tipo de objeto con el que trabajar a la vez. En el siguiente bloque de código, crearemos un ArrayList de enteros sin definir su parámetro de tipo. Es una mala práctica usar genéricos sin parámetros de tipo. El código se compilará, pero devolverá un error en tiempo de ejecución cuando intentemos acceder a sus elementos de otros tipos.

// ~/project/Main.java

import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        ArrayList integerList = new ArrayList();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
        integerList.add("Hello World");

        for (Object element : integerList) {
            System.out.println((Integer)element);
        }
    }
}

Para ejecutar el código, abra la terminal y navegue hasta el directorio ~/project. Compile el código con el siguiente comando:

javac Main.java

Después de una compilación exitosa, ejecute el código con el siguiente comando:

java Main

La salida del código será:

1
2
3
Exception in thread "main" java.lang.ClassCastException: java.base/java.lang.String cannot be cast to java.base/java.lang.Integer
    at Main.main(Main.java:13)

Obtenemos un error en tiempo de ejecución porque la integerList contiene elementos de tipo Integer y String. Esto se debe a que no definimos el parámetro de tipo al crear la integerList.

Comprendiendo la eliminación de tipos en genéricos

Java utiliza la eliminación de tipos en los genéricos para garantizar que no se necesite ningún costo adicional en tiempo de ejecución. El compilador reemplaza el parámetro de tipo con la clase Object o la clase padre (en el caso de genéricos con límite) durante el proceso de eliminación de tipos. En el siguiente bloque de código, veremos cómo los genéricos funcionan con tipos primitivos en tiempo de compilación y tiempo de ejecución.

// ~/project/Main.java

import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        ArrayList<Integer> integerList = new ArrayList<Integer>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);

        int sum = 0;
        for (int i = 0; i < integerList.size(); i++) {
            sum += integerList.get(i);
        }
        System.out.println(sum);
    }
}

Para ejecutar el código, abra la terminal y navegue hasta el directorio ~/project. Compile el código con el siguiente comando:

javac Main.java

Después de una compilación exitosa, ejecute el código con el siguiente comando:

java Main

La salida del código será:

6

En el bloque de código anterior, intentamos agregar los elementos de la integerList. Como sabemos, los tipos primitivos como int no se pueden usar con genéricos. Debido a la eliminación de tipos, el parámetro de tipo Integer se reemplaza con la clase Object en tiempo de compilación. Sin embargo, el autoboxing y el unboxing nos permitieron realizar la adición de enteros en tiempo de compilación. En tiempo de ejecución, el código se ejecutó como un código normal, sin ningún costo adicional.

Resumen

En este laboratorio, aprendimos sobre los genéricos de Java. Cubrimos los conceptos básicos relacionados con las clases genéricas, los métodos genéricos, los métodos genéricos con límite, la seguridad de tipos en los genéricos y la eliminación de tipos en los genéricos. También vimos cómo escribir código flexible y reutilizable utilizando genéricos. Esperamos que este laboratorio te haya ayudado a entender los genéricos en Java.