¿Por qué Exception.fillInStackTrace devuelve Throwable?
-
19-09-2019 - |
Pregunta
Creo que Exception.fillInStackTrace debería devolver Exception o objetos Exception derivados.Considerando las dos funciones siguientes,
public static void f() throws Throwable {
try {
throw new Throwable();
} catch (Exception e) {
System.out.println("catch exception e");
e.printStackTrace();
}
}
public static void g() throws Throwable {
try {
try {
throw new Exception("exception");
} catch (Exception e) {
System.out.println("inner exception handler");
throw e.fillInStackTrace();
}
} catch (Exception e) {
System.out.println("outer exception handler");
e.printStackTrace();
}
}
- El
exception handler
no pudo atrapar elnew Throwable()
en la primera funciónf()
. - El
exception handler
podría atrapar ele.fillInstackTrace()
en la segunda funciong()
. - Pero la segunda función
g()
todavía necesitaríathrows Throwable
.Esto es realmente extraño, ya que pudimos captare.fillInstackTrace()
.
Entonces mi pregunta es ¿por qué Exception.fillInStackTrace no devuelve Exception o Exception-derived en lugar de desarrollar una sintaxis tan extraña?
EDITAR:
A aclarar mi pregunta:Lo que quiero decir con "sintaxis extraña" es
- Desde
Exception.fillInStackTrace()
devolverThrowable
referencia, el controlador de excepciones que recibeException
La referencia no debería poder detectar la excepción. Debido a que Java no permite el abatimiento implícito, debería ser algo comoreturn (Exception)e.fillInstackTrace()
. - Dado que está diseñado para que el controlador de excepciones que recibe
Exception
referencia podría manejar elThrowable
excepción, no es necesario marcar el métodog()
lanzaThrowable
excepción, pero el compilador de Java nos obligaría a hacerlo.
gracias.
Solución
En realidad, es más fácil responder sus preguntas comenzando con la pregunta 2.
Tu preguntaste:2.Dado que está diseñado para que el controlador de excepciones que recibe la referencia de Excepción pueda manejar la excepción Throwable, no es necesario marcar el método g() para lanzar la excepción Throwable, pero el compilador de Java nos obligaría a hacerlo.
Respuesta:En realidad, catch (Excepción e) no puede capturar un Throwable.Prueba esto:
try {
Throwable t = new Throwable();
throw t.fillInStackTrace();
} catch (Exception e) {
System.out.println("outer exception handler");
e.printStackTrace();
}
Verás que la cláusula catch no atrapa el lanzamiento en este caso.
La razón por la que la cláusula catch funciona en su método g() es que cuando invoca throw e.fillInStackTrace()
, la llamada a fillInStackTrace en realidad devuelve una excepción (eso se debe a que e es una excepción en sí misma).Dado que Exception es una subclase de Throwable, eso no contradice la declaración de fillInStackTrace.
Ahora a la primera pregunta.
Tu preguntaste:1.Dado que Exception.fillInStackTrace() devuelve una referencia Throwable, el controlador de excepciones que recibe la referencia de excepción no debería poder detectar la excepción. Debido a que Java no permite el abatimiento implícito, debería ser algo así como return (Exception)e.fillInstackTrace().
Respuesta:Esto no es exactamente un abatimiento implícito.Piense en esto como una variación de la sobrecarga.
digamos que tienes
void process(Throwable t){
...
}
void process(Exception e){
...
}
si llamas process(someObject)
, se determinará en tiempo de ejecución si se llama al primer o segundo método de proceso.De manera similar, si la cláusula catch(Exception e) puede o no capturar su lanzamiento se determinará en tiempo de ejecución, en función de si lanza una excepción o un Throwable.
Otros consejos
Estoy bastante desconcertado por su pregunta. Hay claramente algo acerca de las excepciones de Java / manejo de excepciones que usted no entiende. Así que vamos a empezar por el principio.
En Java, todas las excepciones (en el sentido de que este término se utiliza en la especificación del lenguaje Java) son instancias de una cierta clase que es una subclase de java.lang.Throwable
. Hay dos (y sólo dos) subclases de Throwable directos; es decir java.lang.Exception
y java.lang.Error
. Los casos de todas estas clases ... incluyendo casos de Throwable y error ... se conocen como excepciones en el JLS.
Un controlador de excepciones atrapa excepciones (en el sentido JLS) que son asignación compatible con el tipo de excepción usado en la declaración catch
. Así, por ejemplo:
try {
....
} catch (Exception ex) {
...
}
cogerá cualquier excepción lanzada en el bloque try
que es una instancia de java.lang.Exception
o de un subtipo directa o indirecta de java.lang.Exception
. Pero no va a coger una instancia de java.lang.Throwable
, porque eso es (obviamente) no uno de los anteriores.
Por otro lado:
try {
....
} catch (Throwable ex) {
...
}
atrapar una instancia de java.lang.Throwable
.
Revisión de su ejemplo a la luz de esto, es evidente por qué el método f
no está alcanzando la instancia Throwable: no coincide con el tipo de excepción en la cláusula catch! Por el contrario, en el método g
la instancia Excepción juego con el tipo de excepción en la cláusula de captura y por lo tanto es capturado.
No entiendo lo que está diciendo sobre la necesidad de lanzar un Throwable en g
. En primer lugar, el hecho de que el método declara que lanza Throwable no significa que lo que realmente necesita para lanzarla. Todo lo que está diciendo es que podría tirar algo asignable a throwable ... posiblemente en alguna versión futura del método g
. En segundo lugar, si tuviera que añadir throw e;
al bloque catch exterior, que haría estar lanzando algo que es asignable a throwable.
Por último, es generalmente una mala idea estar creando instancias de Throwable, Excepción, de errores y RuntimeException. Y hay que tener mucho cuidado cuándo y cómo los captura. Por ejemplo:
try {
// throws an IOException if file is missing
InputStream is = new FileInputStream("someFile.txt");
// do other stuff
} catch (Exception ex) {
System.err.println("File not found");
// WRONG!!! We might have caught some completely unrelated exception;
// e.g. a NullPointerException, StackOverflowError,
}
Editar - en los comentarios de OP respuesta:
Pero lo lanzo con e.fillInStackTrace tiro (); debe ser un intance de Throwable, no es la excepción!
El Javadoc dice específicamente que el objeto devuelto es el objeto de excepción que está llamando el método sucesivamente. El propósito del método fillInStacktrace()
es llenar en el seguimiento de la pila para un objeto existente. Si quieres una excepción diferente, usted debe utilizar new
para crear una.
En realidad, me refiero a la gestión de excepciones Outter no debe coger el Throwable lanzada por e.fillInStackTrace tiro ().
he explicado por qué lo hace - porque el Throwable es en realidad la excepción original. ¿Hay algo en mi explicación de que usted no entiende o está simplemente diciendo que no le gusta la forma en que Java se define?
EDIT 2
Y si el manejador de excepciones Outter podría manejar la excepción Throwable, ¿por qué debemos especificar que el método g tiraría excepción Throwable
Se entiende mal lo que estaba diciendo ... que era que si se hizo lanzar una excepción, entonces el throws Throwable
no sería redundante. Otoh, finalmente creo que entiendo su queja.
Creo que el quid de su queja es que no puedes encontrar un error de compilación con esto:
public void function() throws Exception {
try {
throw new Exception();
} catch (Exception ex) {
throw ex.fillInStackTrace();
// according to the static type checker, the above throws a Throwable
// which has to be caught, or declared as thrown. But we "know" that the
// exception cannot be anything other than an Exception.
}
}
Puedo ver que esto es algo inesperado. Pero es inevitable me temo. No hay manera (por debajo de un cambio importante en el tipo de sistema de Java) que se puede declarar el SIgnature del fillInStacktrace
que funcionará en todos los casos. Por ejemplo, si ha movido la declaración del método a la clase de excepción, usted sólo tiene que repetir el mismo problema con los subtipos de Excepción. Pero si se trató de expresar la firma utilizando un parámetro de tipo genérico, que implicaría que fueran todas las subclases de tipos genéricos explícitas Throwable.
Afortunadamente, la cura es muy simple; emitir el resultado de fillInStacktrace()
como sigue:
public void function() throws Exception {
try {
throw new Exception();
} catch (Exception ex) {
throw (Exception) (ex.fillInStackTrace());
}
}
Y el punto final es que es muy raro que una aplicación para llamar explícitamente fillInStacktrace()
. A la luz de esto, sería simplemente no han merecido la pena para los diseñadores de Java para tener "reventado sus entrañas" tratando de resolver esto. Sobre todo porque es realmente sólo un inconveniente menor ... como máximo.
Creo que tendría que preguntar a Frank Yellin (el @author
de java.lang.Exception
). Como han dicho otros, fillInStackTrace()
se declara en Throwable
y documentado para volver this
, por lo que su tipo de retorno debe ser Throwable
. Es sólo heredado por Exception
. Frank podría haber anulado en Exception
, como esto:
public class Exception extends Throwable{
/**Narrows the type of the overridden method*/
@Override
public synchronized Exception fillInStackTrace() {
super.fillInStackTrace();
return this;
}
}
... pero no lo hizo. Antes de Java 1.5, la razón es que se habría producido un error de compilación. En 1,5 y más allá, covariante volver tipos están permitidos y lo anterior es legal.
Pero mi suposición es que si existía la anulación anterior, debería entonces preguntar por qué RuntimeException
no tiene una anulación similares, ¿por qué no anula ArrayStoreException
que invalidación, y así sucesivamente. Así que Frank y amigos probablemente no quieren escribir esos cientos de reemplazos idénticos.
Vale la pena señalar que, si usted está tratando con sus propias clases de excepción personalizada, puede modificar fácilmente el método fillinStackTrace()
como lo he hecho anteriormente, y obtener el tipo más estrecho que el que está buscando.
El uso de los genéricos, uno podría imaginar aumentar la declaración de Throwable
a:
public class Throwable<T extends Throwable<T>>{
public synchronized native T fillInStackTrace();
}
... pero eso no es realmente satisfactoria, por razones que están más allá del alcance de esta respuesta.
fillInStackTrace
devuelve la referencia al mismo objeto. Es el método de encadenamiento para permitir la re-lanzando el Exception
y restablecer seguimiento de la pila de excepción.
public static void m() {
throw new RuntimeException();
}
public static void main(String[] args) throws Throwable {
try {
m();
} catch (Exception e) {
e.printStackTrace();
throw e.fillInStackTrace();
}
}
El tipo de retorno método sólo puede ser de tipo de base que es Throwable
. Se puede generified por lo que el método devuelve el tipo parametrizado. Pero eso no es el caso, por ahora.
RuntimeException e = new RuntimeException();
Throwable e1 = e.fillInStackTrace();
System.out.println(e1.getClass().getName()); //prints java.lang.RuntimeException
System.out.println(e == e1); //prints true
fillInStackTrace()
se define por Throwable
, no Exception
.
No todas las subclases de Throwable
son excepciones (Error
, por ejemplo). En lo que se refiere a Throwable
, lo único que puede garantizar sobre el valor de retorno de fillInStackTrace()
es que es una instancia de Throwable
(ya que sólo devuelve el mismo objeto, como Chandra Patni indique lo contrario).
En realidad fillInStackTrace
devuelve el mismo objeto se invoca en.
e.fillInStackTrace == e
es siempre verdad
Es sólo un acceso directo , también puede simplemente escribir
} catch (Exception e) {
System.out.println("inner exception handler");
e.fillInStackTrace();
throw e;
}
o usar un yeso
throw (Exception) e.fillInStackTrace();
BTW, lo mismo sucede con initCause()
.