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

1091

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

public class GenSet<E> {
    private E a[];

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

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

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

import java.lang.reflect.Array;

class Stack<T> {
    public Stack(Class<T> clazz, int capacity) {
        array = (T[])Array.newInstance(clazz, capacity);
    }

    private final T[] array;
}

Pero realmente no entiendo lo que está pasando.

tatsuhirosatou
fuente
14
¿Realmente necesitas usar una matriz aquí? ¿Qué pasa con el uso de una colección?
mate b
12
Sí, también creo que las colecciones son más elegantes para este problema. Pero esto es para una tarea de clase y se requieren :(
tatsuhirosatou
3
No entiendo por qué necesito reflexionar aquí. La gramática de Java es extraña: como nuevo java.util.HashMap <String, String> [10] no es válido. new java.util.HashMap <long, long> (10) no es válido. nuevo largo [] [10] no es válido, nuevo largo [10] [] es válido. Eso hace que escribir un programa que pueda escribir un programa Java sea más difícil de lo que parece.
hombre de bronce

Respuestas:

704

Tengo que hacer una pregunta a cambio: ¿está GenSet"marcado" o "no marcado"? Qué significa eso?

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

    -> en ese caso, deberías escribir:

    public class GenSet<E> {
    
        private E[] a;
    
        public GenSet(Class<E> 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];
        }
    }
  • Sin marcar : mecanografía débil . En realidad, 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<E> {
    
        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 extends Foo> { // 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 es el resultado de una debilidad conocida y deliberada de los genéricos en Java: se implementó usando el borrado, por lo que las clases "genéricas" no saben con qué argumento de tipo se crearon en tiempo de ejecución y, por lo tanto, no pueden proporcionar tipo- seguridad a menos que se implemente algún mecanismo explícito (verificación de tipo).

Varkhan
fuente
77
¿Cuál sería la mejor opción en cuanto al rendimiento? Necesito obtener elementos de esta matriz con bastante frecuencia (dentro de un bucle). Entonces, una colección es probablemente más lenta, pero ¿cuál de estos dos es más rápido?
user1111929
3
Y si el tipo genérico está acotado, la matriz de respaldo debe ser del tipo delimitador.
Mordechai
55
@AaronDigulla Solo para aclarar que no es asignación, sino inicialización de una variable local. No puede anotar una expresión / declaración.
kennytm
1
@Varkhan ¿Hay alguna manera de cambiar el tamaño de estas matrices desde la implementación de la clase? Por ejemplo, si quiero cambiar el tamaño después del desbordamiento como ArrayList. Busqué la implementación de ArrayList que tienen Object[] EMPTY_ELEMENTDATA = {}para el almacenamiento. ¿Puedo usar este mecanismo para cambiar el tamaño sin conocer el tipo que usa genéricos?
JourneyMan
2
Para aquellos que quieran hacer un método con un tipo genérico (que era lo que estaba buscando), use esto:public void <T> T[] newArray(Class<T> type, int length) { ... }
Daniel Kvist
225

Puedes hacerlo:

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

Esta es una de las formas sugeridas de implementar una colección genérica en Effective Java; Artículo 26 . Sin errores de tipo, no es necesario emitir la matriz repetidamente Sin embargo, esto desencadena una advertencia porque es potencialmente peligroso y debe usarse con precaución. Como se detalla en los comentarios, esto Object[]ahora se está haciendo pasar por nuestro E[]tipo y puede causar errores inesperados oClassCastException haciendo pasar s si se usa de manera insegura.

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


Vale la pena mencionar que, siempre que sea posible, pasará un tiempo mucho más feliz trabajando con Lists en lugar de matrices si usa genéricos. Ciertamente, a veces no tienes otra opción, pero usar el marco de colecciones es mucho más robusto.

dimo414
fuente
47
Esto no funcionará si la matriz se trata como una matriz con tipo de cualquier tipo, como String[] s=b;en el test()método anterior . Eso es porque la matriz de E no es realmente, es Object []. Esto es importante si lo desea, por ejemplo, un List<String>[]- no puede usar un Object[]para eso, debe tener un List[]específicamente. Es por eso que necesita usar la creación de matriz Class <?> Reflejada.
Lawrence Dol
8
El caso / problema de la esquina es si desea hacerlo, por ejemplo, public E[] toArray() { return (E[])internalArray.clone(); }cuando internalArrayse escribe como E[]y, por lo tanto, en realidad es un Object[]. Esto falla en tiempo de ejecución con una excepción de conversión de tipo porque Object[]no se puede asignar a una matriz del tipo Eque sea.
Lawrence Dol
17
Básicamente, este enfoque funcionará siempre que no devuelva la matriz o la pase o la almacene en algún lugar fuera de la clase que requiera una matriz de cierto tipo. Mientras estés dentro de la clase, estás bien porque E se borra. Es "peligroso" porque si intentas devolverlo o algo así, no recibes ninguna advertencia de que no es seguro. Pero si tienes cuidado, entonces funciona.
newacct
3
Es bastante seguro En E[] b = (E[])new Object[1];puede ver claramente que la única referencia a la matriz creada es by que el tipo de bes E[]. Por lo tanto, no hay peligro de que acceda accidentalmente a la misma matriz a través de una variable diferente de un tipo diferente. Si, en cambio, tuvieras que hacerlo, tendrías Object[] a = new Object[1]; E[]b = (E[])a; que ser paranoico sobre cómo lo usas a.
Aaron McDaid
55
Al menos en Java 1.6, esto genera una advertencia: "
Conversión no verificada
61

Aquí le mostramos cómo usar los genéricos para obtener una matriz del tipo que está buscando y al mismo tiempo preservar la seguridad de la fuente (a diferencia de las otras respuestas, que le devolverán una Objectmatriz o generarán advertencias en el momento de la compilación):

import java.lang.reflect.Array;  

public class GenSet<E> {  
    private E[] a;  

    public GenSet(Class<E[]> clazz, int length) {  
        a = clazz.cast(Array.newInstance(clazz.getComponentType(), length));  
    }  

    public static void main(String[] args) {  
        GenSet<String> foo = new GenSet<String>(String[].class, 1);  
        String[] bar = foo.a;  
        foo.a[0] = "xyzzy";  
        String baz = foo.a[0];  
    }  
}

Eso se compila sin advertencias y, como puede ver main, para cualquier tipo de declaración de instancia GenSet, puede asignar aa una matriz de ese tipo, y puede asignar un elemento aa 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 compilador como instancias de java.lang.Class. Para usar uno, simplemente siga el nombre de una clase con .class. Entonces, String.classactúa como un Classobjeto que representa la clase String. Esto también funciona para interfaces, enumeraciones, matrices de cualquier dimensión (p String[].class. Ej. ), Primitivas (p int.class. Ej. ) Y la palabra clave void( p void.class. Ej .).

Classen sí es genérico (declarado como Class<T>, donde Trepresenta el tipo que Classrepresenta el objeto), lo que significa que el tipo de String.classesClass<String> .

Entonces, cada vez que llama al constructor GenSet, pasa un literal de clase para el primer argumento que representa una matriz del GenSettipo declarado de la instancia (por ejemplo, String[].classparaGenSet<String> ). 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, llamar al método castdevuelve el Objectargumento pasado emitido a la clase representada por el Classobjeto en el que se llamó el método. Llamar al método estático newInstanceen java.lang.reflect.Arraydevuelve como una Objectmatriz del tipo representado por el Classobjeto pasado como primer argumento y de la longitud especificada por el intpasado como segundo argumento. Llamar al método getComponentTypedevuelve un Classobjeto que representa el tipo de componente de la matriz representada por el Classobjeto en el que se llamó el método (por ejemplo , String.classpara String[].class, nullsi el Classobjeto no representa una matriz).

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

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

Lo mismo ocurre con cada método Classque devuelve unClass objeto.

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 T[]dará como resultado una advertencia porque el compilador no puede garantizar la seguridad de tipo en ese caso.


Editar con respecto a los comentarios de Ingo:

public static <T> T[] newArray(Class<T[]> type, int size) {
   return type.cast(Array.newInstance(type.getComponentType(), size));
}
gdejohn
fuente
55
Esto es inútil, es solo una forma complicada de escribir una nueva cadena [...]. Pero lo que realmente se necesita es algo como public static <T> T [] newArray (int size) {...}, y esto simplemente no existe en java noir, ¿puede simularse con reflexión? La razón es que la información sobre cómo un tipo genérico es instanciado no está disponible en tiempo de ejecución.
Ingo
44
@Ingo ¿De qué estás hablando? Mi código se puede usar para crear una matriz de cualquier tipo.
gdejohn
3
@Charlatan: Claro, pero también puede ser nuevo []. La pregunta es: quién sabe el tipo y cuándo. Por lo tanto, si todo lo que tiene es un tipo genérico, no puede.
Ingo
2
No lo dudo. El punto es que no obtienes un objeto Class en tiempo de ejecución para el tipo genérico X.
Ingo
2
Casi. Admito que esto es más de lo que se puede lograr con new []. En la práctica, esto casi siempre hará el trabajo. Sin embargo, todavía no es posible, por ejemplo, escribir una clase de contenedor parametrizada con E que tenga un método E [] toArray () y que de hecho devuelva una verdadera matriz E []. Su código podría aplicarse solo cuando hay al menos un objeto E en la colección. Entonces, una solución general es imposible.
Ingo
42

Esta es la única respuesta segura.

E[] a;

a = newArray(size);

@SafeVarargs
static <E> E[] newArray(int length, E... array)
{
    return Arrays.copyOf(array, length);
}
irreputable
fuente
Tuve que buscarlo, pero sí, el segundo argumento de "longitud" Arrays#copyOf()es independiente de la longitud de la matriz suministrada como primer argumento. Eso es inteligente, aunque paga el costo de las llamadas Math#min()y System#arrayCopy(), ninguno de los cuales es estrictamente necesario para hacer este trabajo. docs.oracle.com/javase/7/docs/api/java/util/…
seh
8
Esto no funciona si Ees una variable de tipo. Los varargs crean una matriz de borrado de Ecuándo Ees una variable de tipo, por lo que no es muy diferente de (E[])new Object[n]. Consulte http://ideone.com/T8xF91 . De ninguna manera es más seguro de tipo que cualquier otra respuesta.
Radiodef
1
@Radiodef: la solución es seguro para escribir en tiempo de compilación. tenga en cuenta que la eliminación no es exactamente parte de la especificación del lenguaje; la especificación está escrita cuidadosamente para que podamos tener una reificación completa en el futuro, y luego esta solución también funcionaría perfectamente en tiempo de ejecución, a diferencia de otras soluciones.
ZhongYu
@Radiodef: es discutible si prohibir la creación de matrices genéricas es una buena idea. independientemente, el lenguaje deja una puerta trasera: vararg requiere la creación de una matriz genérica. Es tan bueno como si el idioma lo hubiera permitido new E[]. El problema que mostró en su ejemplo es un problema de borrado general, no exclusivo de esta pregunta y esta respuesta.
ZhongYu
2
@Radiodef: hay algunas diferencias. El compilador verifica la exactitud de esta solución; no se basa en el razonamiento humano del lanzamiento forzado. La diferencia no es significativa para este problema en particular. A algunas personas les gusta ser un poco elegantes, eso es todo. Si alguien se confunde con la redacción de OP, sus comentarios y los míos lo aclaran.
ZhongYu
33

Para extender a más dimensiones, sólo tiene que añadir []parámetros de dimensión a 's y newInstance()( Tes un parámetro de tipo, clses una Class<T>, d1a través d5son números 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 más detalles.

Jason C
fuente
44
+1 Ha habido preguntas sobre la creación de matrices multidimensionales que se cierran como engaños de esta publicación, pero ninguna respuesta ha abordado específicamente eso.
Paul Bellora
1
@JordanC Quizás; aunque es lo mismo en espíritu que stackoverflow.com/a/5671304/616460 ; Pensaré en la mejor manera de manejar el mañana. Tengo sueño.
Jason C
14

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

@FunctionalInterface
interface ArraySupplier<E> {
    E[] get(int length);
}

class GenericSet<E> {
    private final ArraySupplier<E> supplier;
    private E[] array;

    GenericSet(ArraySupplier<E> supplier) {
        this.supplier = supplier;
        this.array    = supplier.get(10);
    }

    public static void main(String[] args) {
        GenericSet<String> ofString =
            new GenericSet<>(String[]::new);
        GenericSet<Double> ofDouble =
            new GenericSet<>(Double[]::new);
    }
}

Por ejemplo, esto es usado por <A> A[] Stream.toArray(IntFunction<A[]>) .

Esto podría también hacerse pre-Java 8 utilizando las clases anónimas pero es más engorroso.

Radiodef
fuente
Realmente no necesita una interfaz especial como ArraySupplieresta, puede declarar el constructor como GenSet(Supplier<E[]> supplier) { ...y llamarlo con la misma línea que tiene.
Lii
44
@Lii Para ser el mismo que mi ejemplo, sería IntFunction<E[]>, pero sí, eso es cierto.
Radiodef
12

Esto está cubierto en el Capítulo 5 (Genéricos) de Effective Java, 2nd Edition , item 25 ... Listas preferidas a las matrices

Su código funcionará, aunque generará una advertencia no verificada (que podría 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 sobre este error / característica en el sitio del proyecto OpenJDK .

Jeff Olson
fuente
8

No es necesario pasar el argumento de clase al constructor. Prueba esto.

public class GenSet<T> {
    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<Integer> intSet = new GenSet<>(3);
System.out.println(intSet);
System.out.println(new GenSet<String>(2));

resultado:

GenSet of java.lang.Integer[3]
GenSet of java.lang.String[2]
saka1029
fuente
7

Los genéricos de Java funcionan verificando los tipos en el momento de la compilación e insertando las conversiones apropiadas, pero borrando los tipos en los archivos compilados. Esto hace que las bibliotecas genéricas sean utilizables por código que no comprende los genéricos (que fue una decisión de diseño deliberada) pero que significa que normalmente no puede averiguar cuál es el tipo en tiempo de ejecución.

El Stack(Class<T> clazz,int capacity)constructor público requiere que pase un objeto de clase en tiempo de ejecución, lo que significa que la información de clase está disponible en tiempo de ejecución para codificarla. Y elClass<T> formulario significa que el compilador verificará que el objeto Class que pasa es precisamente el objeto Class para el tipo T. No es 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 el tipo de los objetos que almacena en su colección verificará sus tipos en el momento en que se agreguen a la colección.

Bill Michell
fuente
6

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

Los genéricos se utilizan para la verificación de tipos durante el tiempo de compilación:

  • Por lo tanto, el propósito es verificar que lo que entra es lo que necesita.
  • Lo que devuelve es lo que necesita el consumidor.
  • Mira esto:

ingrese la descripción de la imagen aquí

No se preocupe por las advertencias de tipografía cuando escriba una clase genérica. Preocúpate cuando lo estés usando.

castigar
fuente
6

¿Qué hay de esta solución?

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

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

Benjamin M
fuente
3
Aseado, pero solo funciona si lo llama 'manualmente', es decir, pasa los elementos individualmente. Si no puede crear una nueva instancia de T[], entonces no puede construir programáticamente un T[] elemspara pasar a la función. Y si pudieras, no necesitarías la función.
orlade
5

Mira también este código:

public static <T> T[] toArray(final List<T> 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.

MatheusJardimB
fuente
Sí, devuelve nulo, que no es la matriz vacía esperada. Es lo mejor que puedes hacer, pero no es lo ideal.
Kevin Cox
Esto también puede fallar si Listtiene más de un tipo de objeto, por ejemplo toArray(Arrays.asList("abc", new Object())), lanzará ArrayStoreException.
Radiodef
Usé una versión simplificada de esto; Lo primero que pude usar fue que funcionó, aunque es cierto que no probé algunas de las soluciones más complicadas. Para evitar un forbucle y otros que utilicé Arrays.fill(res, obj);ya que quería el mismo valor para cada índice.
bbarker
5

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 <T> {
    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<Character> = 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 encontrados en la clase Java.util.ArrayList.

Nikos
fuente
4

El ejemplo está utilizando la reflexión de Java para crear una matriz. Hacer esto generalmente no se recomienda, ya que no es seguro para escribir. En cambio, lo que debe hacer es usar una Lista interna y evitar la matriz.

Ola Bini
fuente
13
El segundo ejemplo (usando Array.newInstance ()) es de hecho typesafe. Esto es posible porque el tipo T del objeto Clase necesita coincidir con la T de la matriz. Básicamente te obliga a proporcionar la información que el tiempo de ejecución de Java descarta para los genéricos.
Joachim Sauer
4

Pasando una lista de valores ...

public <T> T[] array(T... values) {
    return values;
}
Rodrigo Asensio
fuente
3

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

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 el inicio de la matriz donde Array.newInstance (clase de matriz, tamaño de matriz) . La clase puede ser primitiva (int.class) y object (Integer.class).

BeanUtils es parte de la primavera.

Bobster
fuente
3

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

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

donde SIZEes una constante y Tes un identificador de tipo

Pedram Esmaeeli
fuente
1

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

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

Item<K>[] array = new Item[SIZE];

donde Item es una clase que definí que contiene el miembro:

private K value;

De esta forma, obtiene una matriz de tipo K (si el elemento solo tiene el valor) o cualquier tipo genérico que desee definir en la clase Elemento.

vnportnoy
fuente
1

Nadie más ha respondido a la pregunta de lo que está sucediendo en el ejemplo que publicaste.

import java.lang.reflect.Array;

class Stack<T> {
    public Stack(Class<T> clazz, int capacity) {
        array = (T[])Array.newInstance(clazz, capacity);
    }

    private final T[] array;
}

Como otros han dicho, los genéricos se "borran" durante la compilación. Entonces, en 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 genéricos 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 al hacer que el código que llama al constructor (que conoce el tipo) pase un parámetro que le dice a la clase el tipo requerido.

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

Stack<foo> = new Stack<foo>(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 tipo de conversión porque el compilador no tiene forma de saber que la matriz devuelta 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 genéricos que necesitan conocer su tipo de componente en tiempo de ejecución por cualquier razón (creando matrices o instancias de su tipo de componente, etc.).

lavado
fuente
1

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

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

List<Person>[] personLists=new ArrayList<Person>()[10];

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

import java.util.ArrayList;
import java.util.List;


public class PersonList {

    List<Person> people;

    public PersonList()
    {
        people=new ArrayList<Person>();
    }
}

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

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 el momento no hay problemas.

desarrollador747
fuente
0

Puede crear una matriz de Objetos y enviarla a E en todas partes. Sí, no es una forma muy limpia de hacerlo, pero al menos debería funcionar.

Esko
fuente
"Estamos buscando respuestas largas que brinden alguna explicación y contexto. No solo dé una respuesta de una línea; explique por qué su respuesta es correcta, idealmente con citas. Las respuestas sin explicaciones pueden eliminarse".
gparyani
Pero eso no funcionará en algunos casos, como si su clase genérica quiere implementar una interfaz comparable.
RamPrasadBismil
Bienvenido a hace siete años, supongo.
Esko
1
Esto no funcionará si intenta devolver la matriz del código genérico a un llamador no genérico. Habrá una excepción de clase de rascarse la cabeza.
plugwash
0

prueba esto.

private int m = 0;
private int n = 0;
private Element<T>[][] 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<T>();
        }
    }
}
David Bernard
fuente
No puedo ejecutar tu código, ¿de dónde Elementviene tu clase?
0

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

public class Whatever<Thing>{
    private class Holder<OtherThing>{
        OtherThing thing;
    }
    public Holder<Thing>[] arrayOfHolders = new Holder<Thing>[10]
}
StarMonkey
fuente
3
Esto en realidad no funciona. new Holder<Thing>[10]es una creación de matriz genérica.
Radiodef
0

Tal vez no esté relacionado con esta pregunta, pero mientras recibía el " generic array creation" error por usar

Tuple<Long,String>[] tupleArray = new Tuple<Long,String>[10];

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

 Tuple<Long, String>[] tupleArray = new Tuple[10];
Mohsen Afshin
fuente
Sí, esto no está muy relacionado, pero está enraizado en los mismos problemas (borrado, covarianza de matriz). Aquí hay un ejemplo de una publicación sobre la creación de matrices de tipos parametrizados: stackoverflow.com/questions/9542076/…
Paul Bellora
0

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

public T [] createArray(int desiredSize){
    ArrayList<T> builder = new ArrayList<T>();
    for(int x=0;x<desiredSize;x++){
        builder.add(null);
    }
    return builder.toArray(zeroArray());
}

//zeroArray should, in theory, create a zero-sized array of T
//when it is not given any parameters.

private T [] zeroArray(T... i){
    return i;
}

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

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

Cambot
fuente
No, esto no funciona. El varargs crea la eliminación de Tcuando Tes una variable de tipo, es decir, zeroArraydevuelve un Object[]. Ver http://ideone.com/T8xF91 .
Radiodef
0

Podrías usar un yeso:

public class GenSet<Item> {
    private Item[] a;

    public GenSet(int s) {
        a = (Item[]) new Object[s];
    }
}
samir benzenina
fuente
Si va a sugerir esto, realmente necesita explicar sus limitaciones. ¡Nunca exponerse afuera de la clase!
Radiodef
0

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

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

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

GenericInvoker<T>[] 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 de la matriz, debe llamar a la matriz [i] .variable de esta manera:

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

El resto, como cambiar el tamaño de la matriz, se puede hacer con Arrays.copyOf () de esta 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++;
}
Nebulosa del Cangrejo
fuente
1
La pregunta era sobre crear una matriz del tipo del parámetro de tipo genérico T, no una matriz de algún tipo parametrizado.
Sotirios Delimanolis
Sin embargo, completa la misma tarea y no requiere que ingreses a una clase para que tu colección personalizada sea más fácil de usar.
Crab Nebula
¿Qué tarea ? Literalmente es una tarea diferente: una matriz de un tipo paramaterizado frente a una matriz de un parámetro de tipo genérico.
Sotirios Delimanolis
¿Le permite crear una matriz a partir de un tipo genérico? El problema original era inicializar una matriz usando un tipo genérico que usando mi método le permite hacerlo sin tener que hacer que el usuario ingrese en una clase o dé un error no verificado, como intentar convertir un Objeto en una Cadena. Como relajarse, no soy el mejor en lo que hago, y no he ido a la escuela para programar, pero creo que todavía merezco un poco de información en lugar de que otro chico me regañe.
Crab Nebula
Estoy de acuerdo con Sotiros. Hay dos formas de pensar en la respuesta. O es una respuesta a una pregunta diferente, o es un intento de generalizar la pregunta. Ambos están equivocados / no son útiles. Las personas que buscan orientación sobre cómo implementar una clase de "matriz genérica" ​​dejarían de leer cuando lean el título de la pregunta. Y cuando encuentran una Q con 30 respuestas, es muy poco probable que se desplacen hasta el final y lean una respuesta de cero votos de un SO recién llegado.
Stephen C
0

Según vnportnoy, la sintaxis

GenSet<Integer> intSet[] = new GenSet[3];

crea una matriz de referencias nulas, que se completarán como

for (int i = 0; i < 3; i++)
{
   intSet[i] = new GenSet<Integer>();
}

Que es de tipo seguro.

Sam Ginrich
fuente
-1
private E a[];
private int size;

public GenSet(int elem)
{
    size = elem;
    a = (E[]) new E[size];
}
Zubair Ibrhaim
fuente
Siempre debe agregar una explicación a su código y explicar por qué resuelve la pregunta original publicada.
mjuarez
-1

La creación genérica de matrices no está permitida en Java, pero puede hacerlo como

class Stack<T> {
private final T[] array;
public Stack(int capacity) {
    array = (T[]) new Object[capacity];
 }
}
Irfan Ul Haq
fuente