type method generic cast java arrays generics casting type-safety

method - generic type java



Creando una matriz genérica en Java a través de una conversión de tipos no verificada (5)

Si tengo una clase genérica Foo<Bar> , no puedo crear una matriz de la siguiente manera:

Bar[] bars = new Bar[];

(Esto causará el error "No se puede crear una matriz genérica de Barra").

Pero, como lo sugiere dimo414 en una respuesta a esta pregunta (Java cómo: Creación de Arreglo Genérico) , puedo hacer lo siguiente:

Bar[] bars = (Bar[]) new Object[];

(Esto "solo" generará una advertencia: "Seguridad de tipo: conversión sin marcar del objeto [] a la barra []").

En los comentarios que responden a la respuesta de dimo414 , algunas personas afirman que el uso de este constructo puede causar problemas en ciertas situaciones y otros dicen que está bien, ya que la única referencia a la matriz son las bars , que ya son del tipo deseado.

Estoy un poco confundido en qué casos está bien y en qué casos puede meterme en problemas. Los comentarios de newacct y Aaron McDaid , por ejemplo, parecen contradecirse directamente entre sí. Desafortunadamente, el flujo de comentarios en la pregunta original simplemente termina con la pregunta "¿Por qué esto ''ya no es correcto''?", Así que decidí hacerle una nueva pregunta:

Si la bars matriz solo contiene entradas de tipo Bar , ¿podría haber problemas de tiempo de ejecución al usar la matriz o sus entradas? ¿O es el único peligro, que en tiempo de ejecución técnicamente podría convertir la matriz a otra cosa (como String[] ), que luego me permitiría llenarlo con valores de un tipo diferente a Bar ?

Sé que puedo usar Array.newInstance(...) lugar, pero estoy específicamente interesado en el tipo de conversión de tipos anterior, ya que, por ejemplo, en GWT, la newInstance(...) no está disponible.


A diferencia de las listas, los tipos de matriz de Java se reifican , lo que significa que el tipo de Object[] tiempo de ejecución es distinto de la String[] . Por lo tanto, cuando escribes

Bar[] bars = (Bar[]) new Object[];

ha creado una matriz de tipo de tiempo de ejecución Object[] y la ha "convertido" a la Bar[] . Digo "cast" dentro de las comillas porque no es una operación real de conversión marcada: es solo una directiva en tiempo de compilación que le permite asignar un Object[] a una variable de tipo Bar[] . Naturalmente, esto abre la puerta a todo tipo de errores de tipo de tiempo de ejecución. Si realmente creará los errores depende totalmente de su destreza y atención de programación. Por lo tanto, si se siente capaz de hacerlo, entonces está bien hacerlo; Si no lo hace o este código es parte de un proyecto más grande con muchos desarrolladores, entonces es algo peligroso de hacer.


Como me mencionaron en la pregunta, haré el timbre.

Básicamente, no causará ningún problema si no expone esta variable de matriz al exterior de la clase. (Algo así como, Lo que pasa en Las Vegas se queda en Las Vegas.)

El tipo de tiempo de ejecución real de la matriz es Object[] . Por lo tanto, ponerlo en una variable de tipo Bar[] es efectivamente una "mentira", ya que Object[] no es un subtipo de Bar[] (a menos que Object is Bar ). Sin embargo, esta mentira está bien si permanece dentro de la clase, ya que Bar se borra a Object dentro de la clase. (El límite inferior de la Bar es el Object en esta pregunta. En el caso de que el límite inferior de la Bar sea ​​otra cosa, reemplace todas las apariciones del Object en esta discusión con cualquiera que sea ese límite). Sin embargo, si esta mentira se expone de alguna manera al afuera (el ejemplo más simple es devolver la variable de bars directamente como tipo Bar[] , entonces causará problemas.

Para entender lo que realmente está pasando, es instructivo mirar el código con y sin genéricos. Cualquier programa de genéricos se puede reescribir en un programa no genérico equivalente, simplemente eliminando los genéricos e insertando modelos en el lugar correcto. Esta transformación se llama borrado de tipo .

Consideramos una implementación simple de Foo<Bar> , con métodos para obtener y configurar elementos particulares en la matriz, así como un método para obtener la matriz completa:

class Foo<Bar> { Bar[] bars = (Bar[])new Object[5]; public Bar get(int i) { return bars[i]; } public void set(int i, Bar x) { bars[i] = x; } public Bar[] getArray() { return bars; } } // in some method somewhere: Foo<String> foo = new Foo<String>(); foo.set(2, "hello"); String other = foo.get(3); String[] allStrings = foo.getArray();

Después de borrar el tipo, esto se convierte en:

class Foo { Object[] bars = new Object[5]; public Object get(int i) { return bars[i]; } public void set(int i, Object x) { bars[i] = x; } public Object[] getArray() { return bars; } } // in some method somewhere: Foo foo = new Foo(); foo.set(2, "hello"); String other = (String)foo.get(3); String[] allStrings = (String[])foo.getArray();

Así que ya no hay lanzamientos dentro de la clase. Sin embargo, hay conversiones en el código de llamada: cuando se obtiene un elemento y se obtiene la matriz completa. El elenco para obtener un elemento no debe fallar, porque las únicas cosas que podemos poner en la matriz son Bar , por lo que las únicas cosas que podemos obtener también son Bar . Sin embargo, la conversión cuando se obtiene la matriz completa, fallará, ya que la matriz tiene el tipo de tiempo de ejecución real Object[] .

Escrito de forma no genérica, lo que está sucediendo y el problema se vuelven mucho más evidentes. Lo que es especialmente preocupante es que la falla del reparto no se produce en la clase en la que escribimos el reparto en genéricos, sino en el código de otra persona que usa nuestra clase. Y el código de esa otra persona es completamente seguro e inocente. Tampoco sucede en el momento en que hicimos nuestro reparto en el código de genéricos, sino que ocurre más tarde, cuando alguien llama a getArray() , sin previo aviso.

Si no tuviéramos este método getArray() , entonces esta clase sería segura. Con este método, es inseguro. ¿Qué característica lo hace inseguro? Devuelve bars como tipo Bar[] , que depende de la "mentira" que hicimos anteriormente. Dado que la mentira no es cierta, causa problemas. Si el método hubiera devuelto la matriz como tipo Object[] , entonces sería seguro, ya que no depende de la "mentira".

La gente le dirá que no haga un reparto como este, porque causa excepciones en los lugares inesperados como se ve arriba, no en el lugar original donde estaba el elenco no seleccionado. El compilador no le advertirá que getArray() es inseguro (porque desde su punto de vista, dados los tipos que le dijo, es seguro). Por lo tanto, depende del programador ser diligente sobre este escollo y no usarlo de manera insegura.

Sin embargo, yo diría que esto no es un gran problema en la práctica. Cualquier API bien diseñada nunca expondrá las variables de instancia internas al exterior. (Incluso si hay un método para devolver el contenido como una matriz, no devolvería la variable interna directamente; lo copiaría, para evitar que el código externo modifique la matriz directamente). Por lo tanto, ningún método se implementará como getArray() es de todos modos


El elenco:

Bar[] bars = (Bar[]) new Object[];

Es una operación que sucederá en tiempo de ejecución. Si el tipo de Bar[] de tiempo de ejecución Bar[] es distinto de Object[] , esto generará una ClassCastException .

Por lo tanto, si coloca límites en la Bar como en <Bar extends Something> , fallará. Esto se debe a que el tipo de Bar de tiempo de ejecución será Something . Si Bar no tiene límites superiores, entonces su tipo se borrará a Object y el compilador generará todas las conversiones relevantes para colocar objetos en la matriz o para leerlos.

Si intenta asignar bars a algo con un tipo de tiempo de ejecución que no es Object[] (por ejemplo, String[] z = bars ), la operación fallará. El compilador le advierte sobre este caso de uso con la advertencia de "conversión no seleccionada". Entonces, lo siguiente fallará aunque se compile con una advertencia:

class Foo<Bar> { Bar[] get() { return (Bar[])new Object[1]; } } void test() { Foo<String> foo = new Foo<>(); String[] z = foo.get(); }


Ok, he jugado con esta construcción por un momento y puede ser un verdadero desastre.

Creo que la respuesta a mi pregunta es: todo funciona bien siempre y cuando siempre manejes la matriz como genérica. Pero tan pronto como trata de tratarlo de una manera no genérica, hay problemas. Permítanme darles un par de ejemplos:

  • Dentro de Foo<Bar> , puedo crear la matriz como se muestra y trabajar con ella muy bien. Esto se debe a que (si lo comprendo correctamente) el compilador "borra" el tipo Bar y simplemente lo convierte en Object en todas partes. Esencialmente, dentro de Foo<Bar> solo estás manejando un Object[] , lo cual está bien.
  • Pero si tiene una función como esta dentro de Foo<Bar> , que proporciona acceso a la matriz:

    public Bar[] getBars(Bar bar) { Bar[] result = (Bar[]) new Object[1]; result[0] = bar; return result; }

    Puedes tener algunos problemas serios, si lo usas en otro lugar. Aquí hay algunos ejemplos de la locura (la mayor parte de esto tiene sentido, pero parece una locura a primera vista):

    • String[] bars = new Foo<String>().getBars("Hello World");

      causará java.lang.ClassCastException: [Ljava.lang.Object; no se puede convertir a [Ljava.lang.String;

    • for (String bar: new Foo<String>().getBars("Hello World"))

      También causará la misma java.lang.ClassCastException

    • pero

      for (Object bar: new Foo<String>().getBars("Hello World")) System.out.println((String) bar);

      trabajos...

    • Aquí hay uno que no tiene sentido para mí:

      String bar = new Foo<String>().getBars("Hello World")[0];

      también causará la excepción java.lang.ClassCastException , aunque no la esté asignando a un String [] en ninguna parte.

    • Incluso

      Object bar = new Foo<String>().getBars("Hello World")[0];

      causará la misma java.lang.ClassCastException !

    • Solamente

      Object[] temp = new Foo<String>().getBars("Hello World"); String bar = (String) temp[0];

      trabajos...

    y ninguno de estos arroja errores de compilación, por cierto.

  • Ahora, si tienes otra clase genérica, sin embargo:

    class Baz<Bar> { Bar getFirstBar(Bar bar) { Bar[] bars = new Foo<Bar>().getBars(bar); return bars[0]; } }

    Los siguientes trabajos funcionan bien:

    String bar = new Baz<String>().getFirstBar("Hello World");

La mayor parte de esto tiene sentido, una vez que te das cuenta de que, después del borrado de tipo, la función getBars(...) realidad devuelve un Object[] , independiente de la Bar . Esta es la razón por la que no puede (en tiempo de ejecución) asignar el valor de retorno a una String[] sin generar una excepción, incluso si Bar se estableció como String . Sin embargo, la razón por la que esto te impide indexar en la matriz sin tener que volver a convertirlo en un Object[] me supera. La razón por la que las cosas funcionan bien en la clase Baz<Bar> es que la Bar[] allí también se convertirá en un Object[] , independiente de la Bar . Por lo tanto, esto sería equivalente a convertir la matriz en Object[] , luego indexarla y luego devolver la entrada devuelta a String .

En general, después de ver esto, estoy definitivamente convencido de que usar esta forma para crear arreglos es una muy mala idea, a menos que nunca devuelvas el arreglo a un lugar fuera de tu clase genérica. Para mis propósitos, usaré una Collection<...> lugar de una matriz.


Todo funciona bien hasta que quiera usar algo en esa matriz como lo haría con el tipo Bar (o con el tipo con el que inicialice la clase genérica) y no como un Object que realmente es. Por ejemplo teniendo el método:

<T> void init(T t) { T[] ts = (T[]) new Object[2]; ts[0] = t; System.out.println(ts[0]); }

Parece funcionar bien para todos los tipos. Si lo cambias a:

<T> T[] init(T t) { T[] ts = (T[]) new Object[2]; ts[0] = t; System.out.println(ts[0]); return ts; }

y llámalo con

init("asdf");

todavía funciona bien; pero cuando realmente quieres usar la matriz T [] real (que debería ser String [] en el ejemplo anterior):

String[] strings = init("asfd");

entonces tienes un problema, ya que Object[] y String[] son dos clases distintas y lo que tienes es Object[] así que se lanza una ClassCastException .

El problema surge más rápidamente si prueba un tipo genérico acotado:

<T extends Runnable> void init(T t) { T[] ts = (T[]) new Object[2]; ts[0] = t; System.out.println(ts[0]); ts[0].run(); }

Como buena práctica, intente evitar el uso de genéricos y matrices, ya que no se mezclan muy bien entre sí.