¿Cómo crear una matriz genérica en Java?

Debido a la implementación de los generics de Java, no puede tener un código como este:

public class GenSet { private E a[]; public GenSet() { a = new E[INITIAL_ARRAY_LENGTH]; // error: generic array creation } } 

¿Cómo puedo implementar esto manteniendo la seguridad del tipo?

Vi una solución en los foros de Java que dice así:

 import java.lang.reflect.Array; class Stack { public Stack(Class clazz, int capacity) { array = (T[])Array.newInstance(clazz, capacity); } private final T[] array; } 

Pero realmente no entiendo lo que está pasando.

Tengo que hacer una pregunta a cambio: ¿está “marcado” o “desmarcado” su GenSet ? Qué significa eso?

  • Comprobado : mecanografía fuerte . GenSet sabe explícitamente qué tipo de objetos contiene (es decir, su constructor fue llamado explícitamente con un argumento Class , y los métodos arrojarán una excepción cuando se pasen argumentos que no sean de tipo E Consulte Collections.checkedCollection .

    -> en ese caso, debes escribir:

     public class GenSet { private E[] a; public GenSet(Class c, int s) { // Use Array native method to create array // of a type only known at run time @SuppressWarnings("unchecked") final E[] a = (E[]) Array.newInstance(c, s); this.a = a; } E get(int i) { return a[i]; } } 
  • Desmarcado : tipificación débil . No se realiza ninguna verificación de tipo en ninguno de los objetos pasados ​​como argumento.

    -> en ese caso, deberías escribir

     public class GenSet { private Object[] a; public GenSet(int s) { a = new Object[s]; } E get(int i) { @SuppressWarnings("unchecked") final E e = (E) a[i]; return e; } } 

    Tenga en cuenta que el tipo de componente de la matriz debe ser la eliminación del parámetro de tipo:

     public class GenSet { // E has an upper bound of Foo private Foo[] a; // E erases to Foo, so use Foo[] public GenSet(int s) { a = new Foo[s]; } ... } 

Todo esto resulta de una debilidad conocida y deliberada de los generics en Java: se implementó mediante el borrado, por lo que las clases “genéricas” no saben con qué tipo de argumento se crearon en el tiempo de ejecución y, por lo tanto, no pueden proporcionar seguridad a menos que se implemente algún mecanismo explícito (verificación de tipo).

Siempre puedes hacer esto:

 E[] arr = (E[])new Object[INITIAL_ARRAY_LENGTH]; 

Esta es una de las formas sugeridas de implementar una colección genérica en Java efectivo; Artículo 26 . No hay errores de tipo, no es necesario lanzar el conjunto repetidamente. Sin embargo, esto genera una advertencia porque es potencialmente peligroso y debe usarse con precaución. Como se detalla en los comentarios, este Object[] ahora se enmascara como nuestro tipo E[] , y puede causar errores inesperados o ClassCastException s si se usa de forma insegura.

Como regla general, este comportamiento es seguro siempre que el conjunto lanzado se use internamente (por ejemplo, para respaldar una estructura de datos) y no se devuelva ni se exponga al código del cliente. Si necesita devolver una matriz de un tipo genérico a otro código, la clase de Array reflexión que menciona es la forma correcta de hacerlo.


Vale la pena mencionar que siempre que sea posible, tendrás mucho más tiempo para trabajar con List s que con matrices si estás usando generics. Ciertamente, a veces no tienes opción, pero usar el marco de colecciones es mucho más sólido.

Aquí se explica cómo usar generics para obtener una matriz del tipo que se busca al mismo tiempo que se preserva la seguridad del tipo (a diferencia de las otras respuestas, que le devolverán una matriz de Object o generarán advertencias en tiempo de comstackción):

 import java.lang.reflect.Array; public class GenSet { private E[] a; public GenSet(Class clazz, int length) { a = clazz.cast(Array.newInstance(clazz.getComponentType(), length)); } public static void main(String[] args) { GenSet foo = new GenSet(String[].class, 1); String[] bar = foo.a; foo.a[0] = "xyzzy"; String baz = foo.a[0]; } } 

Que comstack sin advertencias, y como puede ver en main , para el tipo que declare una instancia de GenSet , puede asignar a a una matriz de ese tipo, y puede asignar un elemento de a a una variable de ese tipo, lo que significa que la matriz y los valores en la matriz son del tipo correcto.

Funciona mediante el uso de literales de clase como tokens de tipo de tiempo de ejecución, como se explica en los Tutoriales de Java . Los literales de clase son tratados por el comstackdor como instancias de java.lang.Class . Para usar uno, simplemente sigue el nombre de una clase con .class . Entonces, String.class actúa como un objeto Class que representa la clase String . Esto también funciona para interfaces, enumeraciones, matrices de cualquier dimensión (por ejemplo, String[].class ), primitivas (por ejemplo, int.class ) y la palabra clave void (es decir, void.class ).

Class sí es genérica (declarada como Class , donde T representa el tipo que representa el objeto Class ), lo que significa que el tipo de String.class es Class .

Por lo tanto, siempre que llame al constructor para GenSet , debe GenSet un literal de clase para el primer argumento que represente una matriz del tipo declarado de la instancia GenSet (por ejemplo, String[].class para GenSet ). Tenga en cuenta que no podrá obtener una matriz de primitivas, ya que las primitivas no se pueden usar para variables de tipo.

Dentro del constructor, invocar el método de cast devuelve el cast Object pasado a la clase representada por el objeto de Class en el que se invocó el método. Llamar al método estático newInstance en java.lang.reflect.Array devuelve como Object una matriz del tipo representado por el objeto Class pasado como primer argumento y de la longitud especificada por el int pasado como segundo argumento. Llamar al método getComponentType devuelve un objeto Class que representa el tipo de componente de la matriz representada por el objeto Class al que se llamó el método (por ejemplo, String.class para String[].class , null si el objeto Class no representa una matriz) .

Esa última oración no es del todo exacta. Calling String[].class.getComponentType() devuelve un objeto Class que representa la clase String , pero su tipo es Class< ?> , No Class , por lo que no puede hacer algo como lo siguiente.

 String foo = String[].class.getComponentType().cast("bar"); // won't compile 

Lo mismo aplica para todos los métodos en Class que devuelven un objeto Class .

Con respecto al comentario de Joachim Sauer sobre esta respuesta (no tengo suficiente reputación para comentarlo yo mismo), el ejemplo que usa el elenco en T[] dará como resultado una advertencia porque el comstackdor no puede garantizar la seguridad del tipo en ese caso.


Editar con respecto a los comentarios de Ingo:

 public static  T[] newArray(Class type, int size) { return type.cast(Array.newInstance(type.getComponentType(), size)); } 

Esta es la única respuesta que es segura

 E[] a; a = newArray(size); @SafeVarargs static  E[] newArray(int length, E... array) { return Arrays.copyOf(array, length); } 

Para extender a más dimensiones, simplemente agregue los parámetros de [] ‘sy dimensión a newInstance() ( T es un parámetro de tipo, cls es una Class , d1 a d5 son enteros):

 T[] array = (T[])Array.newInstance(cls, d1); T[][] array = (T[][])Array.newInstance(cls, d1, d2); T[][][] array = (T[][][])Array.newInstance(cls, d1, d2, d3); T[][][][] array = (T[][][][])Array.newInstance(cls, d1, d2, d3, d4); T[][][][][] array = (T[][][][][])Array.newInstance(cls, d1, d2, d3, d4, d5); 

Ver Array.newInstance() para detalles.

En Java 8, podemos hacer una especie de creación de matriz genérica utilizando una referencia de lambda o método. Esto es similar al enfoque reflexivo (que pasa una Class ), pero aquí no estamos usando la reflexión.

 @FunctionalInterface interface ArraySupplier { E[] get(int length); } class GenericSet { private final ArraySupplier supplier; private E[] array; GenericSet(ArraySupplier supplier) { this.supplier = supplier; this.array = supplier.get(10); } public static void main(String[] args) { GenericSet ofString = new GenericSet<>(String[]::new); GenericSet ofDouble = new GenericSet<>(Double[]::new); } } 

Por ejemplo, esto es utilizado por A[] Stream.toArray(IntFunction) .

Esto también podría hacerse antes de Java 8 utilizando clases anónimas, pero es más engorroso.

Esto se trata en el Capítulo 5 (Genéricos) de Effective Java, 2nd Edition , elemento 25 … Preferir listas a matrices

Su código funcionará, aunque generará una advertencia no verificada (que puede suprimir con la siguiente anotación:

 @SuppressWarnings({"unchecked"}) 

Sin embargo, probablemente sería mejor usar una lista en lugar de una matriz.

Hay una discusión interesante de este error / función en el sitio del proyecto OpenJDK .

Los generics de Java funcionan comprobando los tipos en tiempo de comstackción e insertando moldes apropiados, pero borrando los tipos en los archivos comstackdos. Esto hace que las bibliotecas genéricas se puedan utilizar mediante código que no comprenda los generics (que fue una decisión de diseño deliberada), lo que significa que normalmente no se puede saber cuál es el tipo en tiempo de ejecución.

El constructor público Stack(Class clazz,int capacity) requiere que pase un objeto Class en tiempo de ejecución, lo que significa que la información de clase está disponible en tiempo de ejecución para codificarla. Y la forma Class significa que el comstackdor verificará que el objeto Class que pase sea precisamente el objeto Class para el tipo T. No una subclase de T, no una superclase de T, sino precisamente T.

Esto significa que puede crear un objeto de matriz del tipo apropiado en su constructor, lo que significa que se verificará el tipo de objetos que almacena en su colección en el punto en que se agregaron a la colección.

Hola, aunque el hilo está muerto, me gustaría llamar su atención sobre esto:

Generics se utiliza para verificar tipos durante el tiempo de comstackción:

  • Por lo tanto, el objective es comprobar que lo que entra es lo que necesita.
  • Lo que devuelve es lo que el consumidor necesita.
  • Mira esto:

enter image description here

No se preocupe acerca de las advertencias de conversión de texto cuando está escribiendo una clase genérica. Preocúpese cuando lo esté usando.

¿Qué hay de esta solución?

 @SafeVarargs public static  T[] toGenericArray(T ... elems) { return elems; } 

Funciona y parece demasiado simple para ser verdad. ¿Hay algún inconveniente?

Mira también este código:

 public static  T[] toArray(final List obj) { if (obj == null || obj.isEmpty()) { return null; } final T t = obj.get(0); final T[] res = (T[]) Array.newInstance(t.getClass(), obj.size()); for (int i = 0; i < obj.size(); i++) { res[i] = obj.get(i); } return res; } 

Convierte una lista de cualquier tipo de objeto en una matriz del mismo tipo.

He encontrado una manera rápida y fácil que funciona para mí. Tenga en cuenta que solo he usado esto en Java JDK 8. No sé si funcionará con versiones anteriores.

Aunque no podemos instanciar una matriz genérica de un parámetro de tipo específico, podemos pasar una matriz ya creada a un constructor de clase genérico.

 class GenArray  { private T theArray[]; // reference array // ... GenArray(T[] arr) { theArray = arr; } // Do whatever with the array... } 

Ahora en main podemos crear la matriz así:

 class GenArrayDemo { public static void main(String[] args) { int size = 10; // array size // Here we can instantiate the array of the type we want, say Character (no primitive types allowed in generics) Character[] ar = new Character[size]; GenArray = new Character<>(ar); // create the generic Array // ... } } 

Para una mayor flexibilidad con sus matrices, puede usar una lista vinculada, por ejemplo. ArrayList y otros métodos que se encuentran en la clase Java.util.ArrayList.

El ejemplo es usar la reflexión de Java para crear una matriz. En general, no se recomienda hacer esto, ya que no es seguro. En cambio, lo que debe hacer es usar una Lista interna y evitar la matriz en absoluto.

Hice este fragmento de código para crear una instancia reflexiva de una clase que se pasa para una herramienta de prueba automatizada simple.

 Object attributeValue = null; try { if(clazz.isArray()){ Class< ?> arrayType = clazz.getComponentType(); attributeValue = Array.newInstance(arrayType, 0); } else if(!clazz.isInterface()){ attributeValue = BeanUtils.instantiateClass(clazz); } } catch (Exception e) { logger.debug("Cannot instanciate \"{}\"", new Object[]{clazz}); } 

Tenga en cuenta este segmento:

  if(clazz.isArray()){ Class< ?> arrayType = clazz.getComponentType(); attributeValue = Array.newInstance(arrayType, 0); } 

para la matriz que inicia donde Array.newInstance (clase de matriz, tamaño de la matriz) . La clase puede ser tanto primitiva (int.class) como object (Integer.class).

BeanUtils es parte de Spring.

No necesita pasar el argumento Clase al constructor. Prueba esto.

 static class GenSet { private final T[] array; @SuppressWarnings("unchecked") public GenSet(int capacity, T... dummy) { if (dummy.length > 0) throw new IllegalArgumentException( "Do not provide values for dummy argument."); Class< ?> c = dummy.getClass().getComponentType(); array = (T[])Array.newInstance(c, capacity); } @Override public String toString() { return "GenSet of " + array.getClass().getComponentType().getName() + "[" + array.length + "]"; } } 

y

 GenSet intSet = new GenSet<>(3); System.out.println(intSet); System.out.println(new GenSet(2)); 

resultado:

 GenSet of java.lang.Integer[3] GenSet of java.lang.String[2] 

En realidad, una forma más sencilla de hacerlo es crear una matriz de objetos y convertirla al tipo deseado, como en el siguiente ejemplo:

 T[] array = (T[])new Object[SIZE]; 

donde SIZE es una constante y T es un identificador de tipo

Pasando una lista de valores …

 public  T[] array(T... values) { return values; } 

El lanzamiento forzado sugerido por otras personas no funcionó para mí, lanzando una excepción de casting ilegal.

Sin embargo, este elenco implícito funcionó bien:

 Item[] array = new Item[SIZE]; 

donde Item es una clase I definida que contiene el miembro:

 private K value; 

De esta forma, obtienes una matriz de tipo K (si el elemento solo tiene el valor) o cualquier tipo genérico que quieras definir en la clase Artículo.

Nadie más ha respondido la pregunta de qué está pasando en el ejemplo que publicó.

 import java.lang.reflect.Array; class Stack { public Stack(Class clazz, int capacity) { array = (T[])Array.newInstance(clazz, capacity); } private final T[] array; } 

Como otros han dicho, los generics se “borran” durante la comstackción. Entonces en el tiempo de ejecución una instancia de un genérico no sabe cuál es su tipo de componente. La razón de esto es histórica, Sun quería agregar generics sin romper la interfaz existente (tanto fuente como binaria).

Las matrices, por otro lado , conocen su tipo de componente en tiempo de ejecución.

Este ejemplo soluciona el problema haciendo que el código que llama al constructor (que sí conoce el tipo) pase un parámetro que indique a la clase el tipo requerido.

Entonces la aplicación construiría la clase con algo así como

 Stack = new Stack(foo.class,50) 

y el constructor ahora sabe (en tiempo de ejecución) cuál es el tipo de componente y puede usar esa información para construir la matriz a través de la API de reflexión.

 Array.newInstance(clazz, capacity); 

Finalmente tenemos un molde de tipo porque el comstackdor no tiene forma de saber que la matriz devuelta por Array#newInstance() es del tipo correcto (aunque lo sepamos).

Este estilo es un poco feo, pero a veces puede ser la solución menos mala para crear tipos generics que necesitan conocer su tipo de componente en tiempo de ejecución por el motivo que sea (creación de matrices o creación de instancias de su tipo de componente, etc.).

Encontré una especie de solución a este problema.

La siguiente línea arroja un error genérico de creación de matriz

 List[] personLists=new ArrayList()[10]; 

Sin embargo, si encapsulo List en una clase separada, funciona.

 import java.util.ArrayList; import java.util.List; public class PersonList { List people; public PersonList() { people=new ArrayList(); } } 

Puede exponer a las personas en la clase PersonList a través de un getter. La línea a continuación le dará una matriz, que tiene una List en cada elemento. En otras palabras, una matriz de List .

 PersonList[] personLists=new PersonList[10]; 

Necesitaba algo como esto en algún código en el que estaba trabajando y esto es lo que hice para que funcione. Hasta ahora no hay problemas.

Podrías crear una matriz de objetos y echarla a E en todas partes. Sí, no es una forma muy limpia de hacerlo, pero al menos debería funcionar.

prueba esto.

 private int m = 0; private int n = 0; private Element[][] elements = null; public MatrixData(int m, int n) { this.m = m; this.n = n; this.elements = new Element[m][n]; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { this.elements[i][j] = new Element(); } } } 

Una solución fácil, aunque desordenada para esto sería anidar una segunda clase de “titular” dentro de su clase principal, y usarla para almacenar sus datos.

 public class Whatever{ private class Holder{ OtherThing thing; } public Holder[] arrayOfHolders = new Holder[10] } 

Tal vez sin relación con esta pregunta, pero mientras obtenía el error de ” generic array creation ” para usar

 Tuple[] tupleArray = new Tuple[10]; 

Descubrí los siguientes trabajos (y funcionó para mí) con @SuppressWarnings({"unchecked"}) :

  Tuple[] tupleArray = new Tuple[10]; 

Me pregunto si este código crearía una matriz genérica efectiva.

 public T [] createArray(int desiredSize){ ArrayList builder = new ArrayList(); for(int x=0;x 

Editar: ¿Quizás una forma alternativa de crear una matriz de este tipo, si el tamaño requerido es pequeño y conocido, sería simplemente alimentar el número requerido de "null" s en el comando zeroArray?

Aunque obviamente esto no es tan versátil como usar el código createArray.

Podrías usar un elenco:

 public class GenSet { private Item[] a; public GenSet(int s) { a = (Item[]) new Object[s]; } } 

De hecho, encontré una solución bastante única para evitar la incapacidad de iniciar una matriz genérica. Lo que tienes que hacer es crear una clase que tome en la variable genérica T como así:

 class GenericInvoker  { T variable; public GenericInvoker(T variable){ this.variable = variable; } } 

y luego en tu clase de arreglo solo haz que comience así:

 GenericInvoker[] array; public MyArray(){ array = new GenericInvoker[]; } 

iniciar un new Generic Invoker[] causará un problema sin marcar, pero en realidad no debería haber ningún problema.

Para obtener desde la matriz, debe llamar a la matriz [i] .variable de la siguiente manera:

 public T get(int index){ return array[index].variable; } 

El rest, como cambiar el tamaño de la matriz, se puede hacer con Arrays.copyOf () de la siguiente manera:

 public void resize(int newSize){ array = Arrays.copyOf(array, newSize); } 

Y la función de agregar se puede agregar así:

 public boolean add(T element){ // the variable size below is equal to how many times the add function has been called // and is used to keep track of where to put the next variable in the array arrays[size] = new GenericInvoker(element); size++; } 
 private E a[]; private int size; public GenSet(int elem) { size = elem; a = (E[]) new E[size]; } 

Generic array creation is disallowed in java but you can do it like

 class Stack { private final T[] array; public Stack(int capacity) { array = (T[]) new Object[capacity]; } }