2009-10-30 14 views
11

(Late edición:. Esta pregunta se espera que sea obsoleta cuando Java 7 viene, debido a la "final rethrow" feature cuales seems like it will be added)¿Cuán seguro es mi regreso seguro?


Muy a menudo, me encuentro en situaciones que parecen esto:

 
    do some initialization 
    try { 
     do some work 
    } catch any exception { 
     undo initialization 
     rethrow exception 
    } 

En C# puede hacerlo de esta manera:

InitializeStuff(); 
try 
{ 
    DoSomeWork(); 
} 
catch 
{ 
    UndoInitialize(); 
    throw; 
} 

Para Java, no hay una buena sustitución, y desde the proposal for improved exception handling was cut from Java 7, parece que tomará, en el mejor de los casos, varios años hasta que obtengamos algo así. Por lo tanto, decidí rodar mi propia:

(Editar:. Medio año después, final rethrow is back, o eso parece)

public final class Rethrow { 

    private Rethrow() { throw new AssertionError("uninstantiable"); } 

    /** Rethrows t if it is an unchecked exception. */ 
    public static void unchecked(Throwable t) { 
     if (t instanceof Error) 
      throw (Error) t; 
     if (t instanceof RuntimeException) 
      throw (RuntimeException) t; 
    } 

    /** Rethrows t if it is an unchecked exception or an instance of E. */ 
    public static <E extends Exception> void instanceOrUnchecked(
      Class<E> exceptionClass, Throwable t) throws E, Error, 
      RuntimeException { 
     Rethrow.unchecked(t); 
     if (exceptionClass.isInstance(t)) 
      throw exceptionClass.cast(t); 
    } 

} 

uso típico:

public void doStuff() throws SomeException { 
    initializeStuff(); 
    try { 
     doSomeWork(); 
    } catch (Throwable t) { 
     undoInitialize(); 
     Rethrow.instanceOrUnchecked(SomeException.class, t); 
     // We shouldn't get past the above line as only unchecked or 
     // SomeException exceptions are thrown in the try block, but 
     // we don't want to risk swallowing an error, so: 
     throw new SomeException("Unexpected exception", t); 
    } 
    private void doSomeWork() throws SomeException { ... } 
} 

Es una Un poco prolijo, capturar Throwable es generalmente mal visto, no estoy muy contento de usar la reflexión solo para volver a lanzar una excepción, y siempre me siento un poco incómodo escribiendo comentarios de "esto no sucederá", pero en la práctica funciona w ell (o parece, al menos). Lo que me pregunto es:

  1. ¿Tengo algún defecto en mis métodos de ayuda al rebrote? ¿Algunos casos de esquina que me he perdido? (Sé que el Throwable puede haber sido causado por algo tan severa que mi undoInitialize se producirá un error, pero está bien.)
    • Alguien ya ha inventado esto? Miré a Commons Lang ExceptionUtils pero eso hace otras cosas.

Editar:

  • finally no es el androide que estoy buscando. Solo me interesa hacer cosas cuando se lanza una excepción.
  • sí, ya sé captura Throwable es un gran no-no, pero yo creo que es el mal menor en comparación con tener aquí tres cláusulas de captura (por Error, RuntimeException y SomeException, respectivamente) con el mismo código.
  • Tenga en cuenta que no estoy tratando de suprimir ningún error, la idea es que cualquier excepción lanzada en el bloque try continuará surgiendo a través de la pila de llamadas tan pronto como haya relanzado algunas cosas.
+1

Tiene una buena razón para atrapar a Throwable. No dejes que te deprima. Es muy parecido al uso de GOTO, "Nunca lo uses, a menos que sea necesario". –

Respuesta

5

Hay un par de maneras de manejar esto. El primero es mi preferencia si no necesita saber cuál fue la excepción.

boolean okay = false; 
try { 
    // do some work which might throw an exception 
    okay = true; 
} finally { 
    if (!okay) // do some clean up. 
} 

En algunos casos, puede hacer lo mismo sin una variable adicional, según lo que haga el bloque try.

Una segunda opción es un truco pero también funciona. método

try { 
    // do some work which might throw an exception 
} catch (Throwable t) { 
    // do something with t. 
    Thread.currentThread().stop(t); 
} 

el tope (Throwable t) no se detiene el hilo, sino que hace que el hilo de lanzar la excepción prevista en una forma sin marcar.

Puede usar Unsafe.throwException() con un poco de manipulación y hay una forma de hacerlo con Generics que he olvidado.

1

Si le preocupa que su no iniciada suceda, entonces quizás quiera poner ese código en un bloque finally, ya que, si se lo llamara en algún momento, entonces quizás debería limpiarlo siempre.

Estoy receloso de detectar Throwable como algunas de las excepciones que quiero manejar, y algunas solo las registro, ya que no sirve de nada aprobar excepciones que el usuario no puede hacer nada, como NullPointerException.

embargo, que no se presentó lo que se define como SomeException, pero si un OutOfMemoryException se lanza, su throwable lo cogerá, pero puede que no sea del mismo tipo que SomeException por lo que se necesita su envoltorio en su función de ejemplo , al menos cuando miro el método instanceOrUnchecked.

Es posible que desee escribir una prueba de unidad, probar diferentes clases de excepciones y ver qué funciona y qué no funciona como se espera, para que pueda documentar el comportamiento esperado.

+0

'SomeException extends Exception'. En cuanto a la desinicialización en 'finalmente', solo quiero que la desinicialización ocurra si se lanza una excepción. Por supuesto, podría tener una bandera booleana inicialmente establecida en falso y luego establecerla en verdadero como la última cosa en el bloque 'try', pero eso es un poco engorroso (y ¿qué pasa si alguien agrega código después de establecerlo en true sin pensar en el manejo de excepciones?). – gustafc

+0

Además, lo he probado, y funciona muy bien ... en todas las circunstancias que he podido pensar, lo que solo demuestra que he logrado escribir código, no soy lo suficientemente inteligente como para romperme a mí mismo =) – gustafc

+1

¿Has probado con una clase que amplía Throwable, no una que amplía Exception? –

1

Una alternativa es tener una fábrica que crea SomeException sólo si la causa es una excepción comprobada:

public static SomeException throwException(String message, Throwable cause) throws SomeException { 
     unchecked(cause); //calls the method you defined in the question. 
     throw new SomeException(message, cause); 
    } 

La razón por la que puse en el valor de retorno del método es para que el cliente pueda hacer algo de esta manera:

 catch (Throwable e) { 
     undoInitialize(); 
     throw SomeException.throwException("message", e); 
    } 

para que el compilador se deja engañar al no requerir un retorno después de la sentencia catch si el método tiene un tipo de retorno, pero todavía produce la excepción si el cliente se olvidó de poner de banda antes de la llamada al método de fábrica.

La desventaja de esto sobre su código es que es menos portátil (funciona para SomeException, pero no para SomeOtherException), pero eso puede estar bien, porque no será para cada tipo de excepción que necesite tener un deshacer inicializar.

Si se ajusta a su caso de uso, podría poner la llamada sin marcar en el constructor de SomeException y tener la lógica disponible para todas las subclases, pero eso debería ajustarse a su proyecto específico; no sería una buena idea en general caso, ya que evitaría envolver las excepciones de tiempo de ejecución.

 public SomeException(message, cause) { 
      super(message, unchecked(cause)); 
     } 

     private static Throwable unchecked(Throwable cause) { 
      if (cause instanceof Error) throw (Error) cause; 
      if (cause instanceof RuntimeException) throw (RuntimeException) cause; 
      return cause; 
     } 
+0

La función de fábrica es agradable, pero como tengo una jerarquía de excepciones (varias clases que incluyen 'SomeException'), necesito algo más flexible. Me gusta la astucia de la segunda alternativa, pero creo que tal vez sea demasiado inteligente: el potencial de WTF para los programadores de mantenimiento parece alto. – gustafc