Creación de una matriz genérica en Java

1. Introducción

Es posible que deseemos utilizar matrices como parte de clases o funciones que admitan genéricos. Debido a la forma en que Java maneja los genéricos, esto puede resultar difícil.

En este tutorial, entenderemos los desafíos de usar genéricos con matrices. Luego, crearemos un ejemplo de una matriz genérica.

También veremos dónde la API de Java ha resuelto un problema similar.

2. Consideraciones al utilizar matrices genéricas

Una diferencia importante entre matrices y genéricos es cómo hacen cumplir la verificación de tipos. Específicamente, las matrices almacenan y verifican información de tipo en tiempo de ejecución. Los genéricos, sin embargo, buscan errores de tipo en tiempo de compilación y no tienen información de tipo en tiempo de ejecución.

La sintaxis de Java sugiere que podríamos crear una nueva matriz genérica:

T[] elements = new T[size];

Pero, si intentáramos esto, obtendríamos un error de compilación.

Para entender por qué, consideremos lo siguiente:

public  T[] getArray(int size) { T[] genericArray = new T[size]; // suppose this is allowed return genericArray; }

Como un tipo T genérico no vinculado se resuelve en Object, nuestro método en tiempo de ejecución será:

public Object[] getArray(int size) { Object[] genericArray = new Object[size]; return genericArray; }

Entonces, si llamamos a nuestro método y almacenamos el resultado en una matriz de cadenas :

String[] myArray = getArray(5);

El código se compilará bien pero fallará en tiempo de ejecución con una ClassCastException . Esto se debe a que acabamos de asignar un objeto [] a una referencia de cadena [] . Específicamente, una conversión implícita del compilador fallaría al convertir Object [] a nuestro tipo requerido String [] .

Aunque no podemos inicializar matrices genéricas directamente, aún es posible lograr la operación equivalente si el código de llamada proporciona el tipo preciso de información.

3. Creación de una matriz genérica

Para nuestro ejemplo, consideremos una estructura de datos de pila limitada MyStack , donde la capacidad se fija a un cierto tamaño. Además, como nos gustaría que la pila funcione con cualquier tipo, una opción de implementación razonable sería una matriz genérica.

Primero, creemos un campo para almacenar los elementos de nuestra pila, que es una matriz genérica de tipo E :

private E[] elements;

Segundo, agreguemos un constructor:

public MyStack(Class clazz, int capacity) { elements = (E[]) Array.newInstance(clazz, capacity); }

Observe cómo usamos java.lang.reflect.Array # newInstance para inicializar nuestra matriz genérica , que requiere dos parámetros. El primer parámetro especifica el tipo de objeto dentro de la nueva matriz. El segundo parámetro especifica cuánto espacio crear para la matriz. Como el resultado de Array # newInstance es de tipo Object , necesitamos convertirlo en E [] para crear nuestra matriz genérica.

También debemos tener en cuenta la convención de nombrar un parámetro de tipo clazz en lugar de class, que es una palabra reservada en Java.

4. Considerando ArrayList

4.1. Usar ArrayList en lugar de una matriz

A menudo es más fácil usar una ArrayList genérica en lugar de una matriz genérica. Veamos cómo podemos cambiar MyStack para usar una ArrayList .

Primero, creemos un campo para almacenar nuestros elementos:

private List elements;

En segundo lugar, en nuestro constructor de pila, podemos inicializar ArrayList con una capacidad inicial:

elements = new ArrayList(capacity);

Hace que nuestra clase sea más simple, ya que no tenemos que usar la reflexión. Además, no estamos obligados a pasar un literal de clase al crear nuestra pila. Finalmente, como podemos establecer la capacidad inicial de una ArrayList , podemos obtener los mismos beneficios que una matriz.

Por lo tanto, solo necesitamos construir matrices de genéricos en situaciones raras o cuando estamos interactuando con alguna biblioteca externa que requiere una matriz.

4.2. Implementación de ArrayList

Curiosamente, ArrayList en sí se implementa utilizando matrices genéricas. Echemos un vistazo dentro de ArrayList para ver cómo.

Primero, veamos el campo de elementos de la lista:

transient Object[] elementData;

Observe que ArrayList usa Object como tipo de elemento. Como nuestro tipo genérico no se conoce hasta el tiempo de ejecución, Object se usa como la superclase de cualquier tipo.

Vale la pena señalar que casi todas las operaciones en ArrayList pueden usar esta matriz genérica ya que no necesitan proporcionar una matriz fuertemente tipada al mundo exterior, excepto por un método: ¡ toArray !

5. Creación de una matriz a partir de una colección

5.1. Ejemplo de LinkedList

Veamos el uso de matrices genéricas en la API de colecciones de Java, donde crearemos una nueva matriz a partir de una colección.

Primero, creemos una nueva LinkedList con un argumento de tipo String y agreguemos elementos:

List items = new LinkedList(); items.add("first item"); items.add("second item"); 

En segundo lugar, construyamos una matriz de los elementos que acabamos de agregar:

String[] itemsAsArray = items.toArray(new String[0]);

Para construir nuestra matriz, List . El método toArray requiere una matriz de entrada. Utiliza esta matriz únicamente para obtener la información de tipo para crear una matriz de retorno del tipo correcto.

En nuestro ejemplo anterior, usamos new String [0] como nuestra matriz de entrada para construir la matriz String resultante .

5.2. Implementación LinkedList.toArray

Echemos un vistazo dentro de LinkedList.toArray , para ver cómo se implementa en Java JDK.

Primero, veamos la firma del método:

public  T[] toArray(T[] a)

En segundo lugar, veamos cómo se crea una nueva matriz cuando es necesario:

a = (T[])java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), size);

Observe cómo hace uso de Array # newInstance para construir una nueva matriz, como en nuestro ejemplo de pila anterior. Además, observe cómo se usa el parámetro a para proporcionar un tipo a Array # newInstance. Finalmente, el resultado de Array # newInstance se convierte en T [] para crear una matriz genérica.

6. Conclusión

En este artículo, primero analizamos las diferencias entre matrices y genéricos, seguido de un ejemplo de creación de una matriz genérica. Luego, mostramos cómo usar una ArrayList puede ser más fácil que usar una matriz genérica. Finalmente, también analizamos el uso de una matriz genérica en la API de colecciones.

Como siempre, el código de ejemplo está disponible en GitHub.