que - ¿Por qué Java no permite subclases genéricas de Throwable?
que es awt y swing en java (5)
De acuerdo con Java Language Sepecification , 3ª edición:
Deseo entender por qué se tomó esta decisión. ¿Qué pasa con las excepciones genéricas?
(Hasta donde yo sé, los genéricos son simplemente azúcar sintáctico en tiempo de compilación, y se traducirán a Object
todos modos en los archivos .class
, declarando efectivamente que una clase genérica es como si todo en ella fuera un Object
. Corrígeme si Me equivoco.)
Aquí hay un ejemplo simple de cómo usar la excepción:
class IntegerExceptionTest {
public static void main(String[] args) {
try {
throw new IntegerException(42);
} catch (IntegerException e) {
assert e.getValue() == 42;
}
}
}
El cuerpo de la declaración TRy arroja la excepción con un valor dado, que es capturado por la cláusula catch.
Por el contrario, la siguiente definición de una nueva excepción está prohibida porque crea un tipo parametrizado:
class ParametricException<T> extends Exception { // compile-time error
private final T value;
public ParametricException(T value) { this.value = value; }
public T getValue() { return value; }
}
Un intento de compilar los informes anteriores muestra un error:
% javac ParametricException.java
ParametricException.java:1: a generic class may not extend
java.lang.Throwable
class ParametricException<T> extends Exception { // compile-time error
^
1 error
Esta restricción es sensata porque casi cualquier intento de atrapar tal excepción debe fallar, porque el tipo no es reificable. Uno podría esperar que el uso típico de la excepción sea algo como lo siguiente:
class ParametricExceptionTest {
public static void main(String[] args) {
try {
throw new ParametricException<Integer>(42);
} catch (ParametricException<Integer> e) { // compile-time error
assert e.getValue()==42;
}
}
}
Esto no está permitido, porque el tipo en la cláusula catch no es reificable. En el momento de escribir esto, el compilador de Sun informa una cascada de errores de sintaxis en tal caso:
% javac ParametricExceptionTest.java
ParametricExceptionTest.java:5: <identifier> expected
} catch (ParametricException<Integer> e) {
^
ParametricExceptionTest.java:8: '')'' expected
}
^
ParametricExceptionTest.java:9: ''}'' expected
}
^
3 errors
Como las excepciones no pueden ser paramétricas, la sintaxis está restringida, por lo que el tipo debe escribirse como un identificador, sin el siguiente parámetro.
Como señaló la marca, los tipos no son reificables, lo cual es un problema en el siguiente caso:
try {
doSomeStuff();
} catch (SomeException<Integer> e) {
// ignore that
} catch (SomeException<String> e) {
crashAndBurn()
}
Tanto SomeException<Integer>
como SomeException<String>
se borran al mismo tipo, no hay forma de que la JVM distinga las instancias de excepción y, por lo tanto, no hay forma de saber qué bloque catch
se debe ejecutar.
Esencialmente porque fue diseñado de una mala manera.
Este problema evita el diseño abstracto limpio, por ejemplo,
public interface Repository<ID, E extends Entity<ID>> {
E getById(ID id) throws EntityNotFoundException<E, ID>;
}
El hecho de que una cláusula de captura fallaría para los genéricos no se reificará no es excusa para eso. El compilador simplemente podría rechazar los tipos genéricos concretos que amplían Throwable o no permiten los genéricos dentro de las cláusulas catch.
Esperaría que sea porque no hay forma de garantizar la parametrización. Considera el siguiente código:
try
{
doSomethingThatCanThrow();
}
catch (MyException<Foo> e)
{
// handle it
}
Como nota, la parametrización es solo azúcar sintáctica. Sin embargo, el compilador intenta garantizar que la parametrización se mantenga constante en todas las referencias a un objeto en el ámbito de compilación. En el caso de una excepción, el compilador no tiene forma de garantizar que MyException solo se lanza desde un ámbito que está procesando.
Los genéricos se verifican en tiempo de compilación para la corrección de tipo. La información de tipo genérico se elimina en un proceso llamado borrado de tipo . Por ejemplo, List<Integer>
se convertirá en la List
tipo no genérico.
Debido a la eliminación de tipo , los parámetros de tipo no se pueden determinar en tiempo de ejecución.
Supongamos que se le permite extender Throwable
esta manera:
public class GenericException<T> extends Throwable
Ahora consideremos el siguiente código:
try {
throw new GenericException<Integer>();
}
catch(GenericException<Integer> e) {
System.err.println("Integer");
}
catch(GenericException<String> e) {
System.err.println("String");
}
Debido al borrado de tipo , el tiempo de ejecución no sabrá qué bloque de captura ejecutar.
Por lo tanto, es un error en tiempo de compilación si una clase genérica es una subclase directa o indirecta de Throwable.