new - ¿Por qué Java no admite Throwables genéricos?
new icon java (5)
class Bouncy<T> extends Throwable {
}
// Error: the generic class Bouncy<T> may not subclass java.lang.Throwable
¿Por qué Java no admite los Throwable
s genéricos?
Me doy cuenta de que el borrado de tipos complica ciertas cosas, pero obviamente Java ya se las arregla mucho, así que ¿por qué no empujarlo un poco más y permitir Throwable
s genéricos, con una exhaustiva verificación en tiempo de compilación para problemas potenciales?
Siento que el argumento del borrado de tipo es bastante débil. Actualmente, no podemos hacer:
void process(List<String> list) {
}
void process(List<Integer> list) {
}
Por supuesto, nos las arreglamos sin él. No estoy pidiendo que seamos capaces de catch Bouncy<T1>
y Bouncy<T2>
en el mismo bloque try
, pero si los usamos en contextos separados con reglas estrictas de cumplimiento en tiempo de compilación (lo cual es bastante parecido) los genéricos funcionan en este momento), ¿no sería viable?
Aquí hay un par de cosas que puedes hacer:
- Los dispositivos desechables pueden implementar interfaces genéricas, siempre que el propio lanzable no tenga parámetros de tipo, por ejemplo,
interface Bouncy<E> {
// ...
}
class BouncyString extends Exception implements Bouncy<String> {
// ...
}
- Una cláusula de
throws
puede referirse a parámetros de tipo, por ejemplo,static <X extends Throwable> void
throwIfInstanceOf(Throwable ex, Class<X> clazz) throws X {
if (clazz.isInstance(ex)) throw clazz.cast(ex);
}
Especificación del lenguaje Java 8.1.2 Clases genéricas y parámetros de tipo :
Esta restricción es necesaria ya que el mecanismo de captura de la máquina virtual Java funciona solo con clases no genéricas.
Personalmente, creo que es porque no podemos obtener ningún beneficio de los genéricos dentro de una cláusula catch
. No podemos escribir catch (Bouncy<String> ex)
debido al borrado de tipo, pero si escribimos catch (Bouncy ex)
, sería inútil convertirlo en genérico.
Respuesta corta: porque tomaron atajos, como lo hicieron con el borrado.
Respuesta larga: como otros ya lo han indicado, debido al borrado, no hay forma de marcar una diferencia en el tiempo de ejecución entre "catch MyException <String>" y "catch MyException <Integer>".
Pero eso no significa que no haya necesidad de excepciones genéricas . ¡Quiero que los genéricos puedan usar campos genéricos! Podrían simplemente haber permitido excepciones genéricas, pero solo permitir la captura en el estado sin procesar (por ejemplo, "catch MyException").
Por supuesto, esto haría que los genéricos sean aún más complicados. Esto es para mostrar cuán mala fue la decisión de borrar los genéricos. ¿Cuándo tendremos una versión de Java que admita genéricos reales (con RTTI), no el azúcar sintáctico actual?
Tipo de borrado. El tipo de excepción de tiempo de ejecución no tiene información genérica. Asi no puedes hacer
} catch( Mistake<Account> ea) {
...
} catch( Mistake<User> eu) {
...
}
todo lo que puedes hacer es
catch( Mistake ea ) {
...
}
Y el borrado de tipo es la forma en que se decidió preservar la compatibilidad con versiones anteriores cuando Java se movía de 1.4 a 1.5. Mucha gente era infeliz entonces, y con razón. Pero teniendo en cuenta la cantidad de código desplegado, era impensable romper el código que funcionaba felizmente en 1.4.
Todavía puedes usar métodos genéricos, como este:
public class SomeException {
private final Object target;
public SomeException(Object target) {
this.target = target;
}
public <T> T getTarget() {
return (T) target;
}
}
....
catch (SomeException e) {
Integer target = e.getTarget();
}
Estoy de acuerdo con la respuesta de Cristian arriba. Si bien la respuesta aceptada es técnicamente correcta (en la medida en que hace referencia a las especificaciones de JVM), la respuesta de Cristian Vasile es una que califica e incluso desafía la limitación.
Hay al menos dos argumentos que noté en las respuestas a esta pregunta con los que no estoy de acuerdo y que voy a refutar. Si los argumentos en estas respuestas fueran correctos, podríamos usarlos para atacar los genéricos en otros contextos donde se usan con éxito hoy.
El primer argumento dice que no podemos usar esto:
catch (Exception<T1> e) {}
porque la JVM no sabe cómo trabajar con Exception<T1>
. Este argumento parece atacar también este uso de genéricos, sobre la base de que la JVM no sabe cómo usar la List<T1>
:
List<T1> list;
El argumento, por supuesto, olvida que el compilador realiza el borrado de tipo y, por lo tanto, la JVM no necesita saber cómo manejar la Exception<T1>
. Simplemente puede manejar la Exception
, al igual que la List
.
Por supuesto, nunca podríamos manejar la catch(Exception<T1> e)
y la catch(Exception<T2> e)
en el mismo intento / captura debido al borrado de tipo, pero nuevamente, eso no es peor que con argumentos de método o valores de retorno hoy. : tampoco manejamos myMethod(List<T1>)
y myMethod(List<T2>)
hoy ... (Reitero este aspecto en la segunda refutación a continuación).
Un segundo argumento es el siguiente. No permitimos esto:
catch (Exception<T1> e) {}
porque esto no funcionaria
catch (Exception<T1> e) {}
catch (Exception<T2> e) {}
OK, entonces ¿por qué no rechazar esto?
interface MyInterface {
Comparable<Integer> getComparable();
}
porque esto no funciona
interface MyInterface {
Comparable<Integer> getComparable();
Comparable<String> getComparable();
}
o esto:
interface MyInterface {
void setComparable(Comparable<Integer> comparable);
}
porque esto no funciona
interface MyInterface {
void setComparable(Comparable<Integer> comparable);
void setComparable(Comparable<String> comparable);
}
En otras palabras, ¿por qué no rechazar los genéricos en la mayoría de los casos?
Este segundo argumento olvida que, aunque no podríamos permitir diferentes construcciones genéricas que borren a la misma construcción no genérica en estos contextos, aún podemos hacer la siguiente mejor opción y permitir los genéricos siempre que los tipos no se borren. al mismo tipo . Eso es lo que hacemos con los parámetros del método: le permitimos usar genéricos, pero nos quejamos tan pronto como detectamos firmas duplicadas después del borrado de tipo. Bueno, podríamos haber hecho lo mismo con excepciones y bloques catch ...
En conclusión, me gustaría ampliar la respuesta de Cristian. En lugar de permitir clases de excepción genéricas y usar los tipos sin procesar en bloques catch
:
class MyException<T> {}
...
catch (MyException e) { // raw
Java podría haber recorrido todo el camino sin problemas:
class MyException<T> {}
...
catch (MyException<Foo> e) {