2010-12-02 15 views
11

Estoy intentando crear un método de ayuda para convertir la lista de dos líneas en conversión de matriz en una sola línea. El problema con el que me he encontrado es que no estoy seguro de cómo crear una instancia T [].Crear una instancia de matriz genérica en un método genérico

He intentado

Array.newInstance(T.class, list.size) pero no puedo darle de comer T.class ..

también trató new T[](list.size) pero no le gusta los parámetros.

public <T> T[] ConvertToArray(List<T> list) 
{ 
    T[] result = ??? 

    result = list.toArray(result); 

    return result; 
} 

¿Alguna otra idea?

Gracias

+0

Gracias por arreglar mi ToArray S.P .. ¿puedes decir que soy un desarrollador natural de C#? : D –

Respuesta

12

No se pueden mezclar genéricos y matrices así. Los genéricos tienen verificación en tiempo de compilación, las matrices tienen verificación en tiempo de ejecución, y esos enfoques son en su mayoría incompatibles. Al principio lo sugerí:

@SuppressWarnings("unchecked") 
public <T> T[] ConvertToArray(List<T> list) 
{  
    Object[] result = new Object[list.size()]; 
    result = list.toArray(result); 
    return (T[])result; 
} 

Esto está mal de un modo sigiloso, como al menos una otra persona aquí pensaron que iba a funcionar! Sin embargo, cuando lo ejecuta, obtiene un error de tipo incompatible, porque no puede convertir un Objeto [] en un Entero []. ¿Por qué no podemos obtener T.class y crear una matriz del tipo correcto? O ¿new T[]?

Los genéricos usan tipo borrado para conservar la compatibilidad con versiones anteriores. Se controlan en tiempo de compilación, pero se eliminan del tiempo de ejecución, por lo que el bytecode es compatible con las JVM pregenéricas. Esto significa que no puede tener conocimiento de clase de una variable genérica en tiempo de ejecución.

Así, mientras que usted puede garantizar que habrá T[] result del tipo Integer [] antes de tiempo, el código list.toArray(result); (o new T[] o Array.newInstance(T.class, list.size());) realizarán siempre en tiempo de ejecución, y no se puede saber qué es T!

Aquí hay una versión que hace trabajo, como recompensa por la lectura de esa conferencia:

public static <T> T[] convertToArray(List<?> list, Class<T> c) { 
    @SuppressWarnings("unchecked") 
    T[] result = (T[]) Array.newInstance(c, list.size()); 
    result = list.toArray(result); 
    return (T[]) result; 
} 

en cuenta que tenemos un segundo parámetro para proporcionar la clase en tiempo de ejecución (así como en tiempo de compilación a través de los genéricos) Se podría utilizar este modo:

Integer[] arrayOfIntegers = convertToArray(listOfIntegers, Integer.class); 

¿Vale la pena la molestia? Todavía tenemos que suprimir una advertencia, ¿entonces definitivamente es seguro?

Mi respuesta es sí. La advertencia generada allí es solo un "No estoy seguro" del compilador. Al recorrerlo, podemos confirmar que el lanzamiento siempre tendrá éxito, incluso si coloca la clase incorrecta como el segundo parámetro, se lanza una advertencia en tiempo de compilación.

La principal ventaja de hacer esto es que hemos centralizado la advertencia en un solo lugar. Solo necesitamos probar este lugar correcto, y sabemos que el código siempre tendrá éxito. Para citar la documentación de Java:

el lenguaje está diseñado para garantizar que si toda su aplicación se ha compilado sin advertencias sin marcar usando javac -source 1.5, que es un tipo seguro [1]

Así que ahora en lugar de tener estas advertencias en todo su código, es solo en un lugar, y puede usar esto sin tener que preocuparse, existe un riesgo enormemente reducido de que se equivoque al usarlo.

Es posible que también desee consultar this SO answer que explica el problema con más profundidad, y this answer que fue mi hoja de cuna al escribir esto. Además del already cited Java documentation, otra referencia útil que utilicé fue this blog post por Neal Gafter, ex ingeniero de personal de Sun Microsystems y co-diseñador de las características del lenguaje 1.4 y 5.0.

Y, por supuesto, ¡gracias a ShaneC que acertadamente señaló que mi primera respuesta falló en el tiempo de ejecución!

+0

El problema con esto es que si lo llama entero [] results = convertToArray (listOfIntegers) obtendrá un error que no puede lanzar java.lang.Object a java.lang.Integer –

+0

ShaneC es correcto, esto no funciona Lo siento, no tenía un compilador de Java a mano cuando escribí esto. – ZoFreX

+0

¡Reparado! Gracias Shane! – ZoFreX

2

Si no se puede pasar en T.class, a continuación, que está básicamente atornillados. La eliminación del tipo significa que simplemente no sabrá el tipo de T en el momento de la ejecución.

Por supuesto, hay otras formas de especificar tipos, como super type tokens, pero supongo que si no puede pasar el T.class, tampoco podrá pasar un token de tipo. Si puedes, entonces eso es genial :)

+0

Eso me parece demasiado exagerado ... * suspiro * –

+0

@Ed: Reparado, gracias. @ShaneC: Escriba que el borrado es una mierda, básicamente ... especialmente si está acostumbrado a la implementación muy diferente de genéricos de .NET. –

+0

@Jon Skeet dame una versión de plataforma cruzada/de código abierto de C# sin Microsoft en cualquier parte y saltaré sobre ella en un instante –

1

El problema es que, debido al borrado de tipos, una Lista no sabe su tipo de componente en tiempo de ejecución, mientras que el tipo de componente es lo que necesitaría para crear la matriz.

Así que tienes las dos opciones que se encuentran en todo el API:

La única otra posibilidad que se me ocurre sería una gran molestia:

  • Revise todos los elementos de la lista y encuentre el "Divisor común más grande", la clase o interfaz más específica que extienden o implementan todos los artículos.
  • crear una matriz de este tipo

Pero eso va a ser mucho más líneas que el dos (y también puede dar lugar a código de cliente haciendo suposiciones no válidas).

+0

Tenga en cuenta que mi camino no funcionará, ¡así que solo hay una opción! – ZoFreX

+0

Actualización: Corregí mi camino, pero ahora es lo mismo que la respuesta de Jon Skeet. Aún solo una opción. – ZoFreX

+0

Este método tiene el problema de que una 'Lista ' que solo contiene instancias 'Cat' significa que devolverá' Cat [] 'en lugar de' Animal [] ', lo que hará que falle otro código cuando intente pegarse un 'Perro' en la matriz resultante. – Gabe

-1

Esto es lo más cercano que puedo hacer para hacer lo que desea. Esto supone suponer que usted conoce la clase en el momento en que escribe el código y que todos los objetos de la lista son de la misma clase, es decir, no hay subclases. Estoy seguro de que esto podría hacerse más sofisticado para aliviar la suposición de que no hay subclases, pero no veo cómo en Java se podía evitar la suposición de conocer la clase en el momento de la codificación.

package play1; 

import java.util.*; 

public class Play 
{ 
    public static void main (String args[]) 
    { 
    List<String> list=new ArrayList<String>(); 
    list.add("Hello"); 
    list.add("Shalom"); 
    list.add("Godspidanya"); 

    ArrayTool<String> arrayTool=new ArrayTool<String>(); 
    String[] array=arrayTool.arrayify(list); 

    for (int x=0;x<array.length;++x) 
    { 
     System.out.println(array[x]); 
    } 
    } 
} 
class ArrayTool<T> 
{ 
    public T[] arrayify(List<T> list) 
    { 
    Class clazz=list.get(0).getClass(); 
    T[] a=(T[]) Array.newInstance(clazz, list.size()); 
    return list.toArray(a); 
    } 
} 
0

¿Esto realmente necesita ser cambiado a un trazador de líneas? Con cualquier clase concreta, ya se puede hacer una lista para la conversión de matriz en una línea:

MyClass[] result = list.toArray(new MyClass[0]); 

Por supuesto, esto no funcionará para los argumentos genéricos en una clase.

Consulte Joshua Bloch's Effective Java Second Edition, artículo 25: Preferir listas para la matriz (pp 119-123). Que es parte de sample chapter PDF.

+0

debe hacer 'MyClass [] result = list.toArray (new MyClass [list.size()]);', luego la matriz que pasó puede reutilizarse. –

Cuestiones relacionadas