¿Hay alguna forma de hacer que run () de Runnable arroje una excepción?


80

Un método al que estoy llamando en run () en una clase que implementa Runnable ) está diseñado para lanzar una excepción.

Pero el compilador de Java no me deja hacer eso y sugiere que lo rodee con try / catch.

El problema es que rodeándolo con un try / catch hago que ese run () en particular sea inútil. Yo no quiero tirar esa excepción.

Si especifico throwspara run () en sí, el compilador se queja de que Exception is not compatible with throws clause in Runnable.run().

Por lo general, estoy totalmente de acuerdo con no dejar que run () arroje una excepción. Pero tengo una situación única en la que debo tener esa funcionalidad.

¿Cómo puedo solucionar esta limitación?


Además de otras respuestas, para realizar un seguimiento del progreso de la tarea, puede utilizar la clase FutureTask.
JProgrammer

Respuestas:


26

Si desea pasar una clase que se implementa Runnableen el Threadmarco, entonces debe seguir las reglas de ese marco, consulte la respuesta de Ernest Friedman-Hill por qué hacerlo de otra manera es una mala idea.

Sin embargo, tengo el presentimiento de que desea llamar al runmétodo directamente en su código, para que su código de llamada pueda procesar la excepción.

La respuesta a este problema es sencilla. No use la Runnableinterfaz de la biblioteca Thread, sino que cree su propia interfaz con la firma modificada que permite lanzar una excepción marcada, por ejemplo

public interface MyRunnable
{
    void myRun ( ) throws MyException;
}

Incluso puede crear un adaptador que convierta esta interfaz en real Runnable(manejando la excepción marcada) adecuado para su uso en el marco de Thread.


Se llegó a una solución tan simple simplemente sin pensar "en la caja". Por supuesto, una Runnablees solo una interfaz simple y podemos crear la nuestra. No es útil para el caso de uso de subprocesos, pero es perfecto para pasar diferentes fragmentos de código "ejecutables".
Richard Le Mesurier

82

En su Callablelugar, puede usar un , enviándolo a un ExecutorServicey esperando el resultado FutureTask.isDone()devuelto por el ExecutorService.submit().

Cuando isDone()devuelve verdadero, llamas FutureTask.get(). Ahora, si Callableha lanzado un Exception, FutureTask.get()también lanzará un Exceptiony la Excepción original podrá acceder usando Exception.getCause().


23

Si run()lanzara una excepción marcada, ¿qué la detectaría? No hay forma de incluir esa run()llamada en un controlador, ya que no escribe el código que lo invoca.

Puede detectar su excepción marcada en el run()método y lanzar una excepción sin marcar (es decir, RuntimeException) en su lugar. Esto terminará el hilo con un seguimiento de pila; quizás eso es lo que buscas.

Si, en cambio, desea que su run()método informe el error en algún lugar, entonces puede proporcionar un método de devolución de llamada para que el bloque run()del método catchllame; ese método podría almacenar el objeto de excepción en algún lugar, y luego su hilo interesado podría encontrar el objeto en esa ubicación.


2
La primera parte no es un buen argumento. "Si main()arrojara una excepción marcada, ¿qué la atraparía?" "Si run()lanza una excepción sin marcar, ¿qué la atraparía?"
Christian Hujer

17

Sí, hay una forma de lanzar una excepción marcada del run()método, pero es tan terrible que no la compartiré.

Esto es lo que puede hacer en su lugar; utiliza el mismo mecanismo que ejercería una excepción de tiempo de ejecución:

@Override
public void run() {
  try {
    /* Do your thing. */
    ...
  } catch (Exception ex) {
    Thread t = Thread.currentThread();
    t.getUncaughtExceptionHandler().uncaughtException(t, ex);
  }
}

Como han señalado otros, si su run()método es realmente el objetivo de a Thread, no tiene sentido lanzar una excepción porque no es observable; lanzar una excepción tiene el mismo efecto que no lanzar una excepción (ninguna).

Si no es un Threadobjetivo, no lo use Runnable. Por ejemplo, quizás Callablesea ​​más adecuado.


¿Pero esto hace que el proceso se bloquee cuando se lanza?
Dinesh

@DineshVG No, solo un error en la JVM puede causar un verdadero bloqueo. El controlador de excepciones predeterminado simplemente imprime la excepción. Si está acostumbrado a ver salir el proceso después de eso, es porque ese hilo era el único hilo que se estaba ejecutando y terminó.
erickson

Intenté (en casos de prueba de instrumentación de Android) usar esto para una llamada de jabón, donde si obtengo un 400 de una llamada de jabón, lanzo una excepción. Esta llamada de jabón se llama desde un hilo al iniciar el caso de prueba. Este hilo usa esto t.getUncaughtExceptionHandler().uncaughtException(t, ex);para lanzarlo al caso de prueba de instrumentación. Agregar esta línea hace que el proceso se bloquee. No sé por qué.
Dinesh

@DineshVG En ese entorno, ¿ Thread.getDefaultUncaughtExceptionHandler()vuelve null? Si no es así, ¿cuál es el tipo de resultado? ¿Qué sucede si, en lugar de llamar uncaughtException(), envuelve la excepción marcada en a RuntimeExceptiony la lanza?
erickson

1
@DineshVG Android puede estar configurando un controlador de excepción no detectado predeterminado que hace esto. Por eso le pregunté si Thread.getDefaultUncaughtExceptionHandler()regresa null; de lo contrario, Android proporciona un valor predeterminado para ofrecer informes, etc. Pero puede configurarlo para que haga lo que quiera. Hay más información aquí.
erickson

6
@FunctionalInterface
public interface CheckedRunnable<E extends Exception> extends Runnable {

    @Override
    default void run() throws RuntimeException {
        try {
            runThrows();
        }
        catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }

    void runThrows() throws E;

}

1

Algunas personas intentan convencerte de que tienes que seguir las reglas. Escuche, pero si obedece, debe decidir usted mismo dependiendo de su situación. La realidad es que "DEBES jugar según las reglas" (no "DEBES seguir las reglas"). Solo tenga en cuenta que si no cumple con las reglas, puede haber consecuencias.

La situación no solo se aplica en la situación de RunnableJava 8, sino también con mucha frecuencia en el contexto de Streams y otros lugares donde se han introducido interfaces funcionales sin la posibilidad de lidiar con excepciones comprobadas. Por ejemplo, Consumer, Supplier, Function, BiFunctiony así sucesivamente todos han sido declarados sin instalaciones para lidiar con excepciones comprobadas.

Entonces, ¿cuáles son las situaciones y las opciones? En el texto siguiente, Runnablees representativo de cualquier interfaz funcional que no declara excepciones, o declara excepciones demasiado limitadas para el caso de uso en cuestión.

  1. Usted mismo ha declarado en Runnablealgún lugar y podría reemplazarlo Runnablecon otra cosa.
    1. Considere reemplazar Runnablecon Callable<Void>. Básicamente lo mismo, pero permite lanzar excepciones; y tiene que hacerlo return nullal final, lo cual es una leve molestia.
    2. Considere reemplazar Runnablecon su propia costumbre @FunctionalInterfaceque puede lanzar exactamente las excepciones que desee.
  2. Ha utilizado una API y hay alternativas disponibles. Por ejemplo, algunas API de Java están sobrecargadas, por lo que podría usar en Callable<Void>lugar de Runnable.
  3. Ha utilizado una API y no hay alternativas. En ese caso, todavía no te quedan opciones.
    1. Puede envolver la excepción en RuntimeException.
    2. Puede piratear la excepción en una RuntimeException utilizando una conversión sin marcar.

Puedes probar lo siguiente. Es un truco, pero a veces lo que necesitamos es un truco. Porque, si una excepción debe estar marcada o desmarcada se define por su tipo, pero prácticamente debería estar definida por la situación.

@FunctionalInterface
public interface ThrowingRunnable extends Runnable {
    @Override
    default void run() {
        try {
            tryRun();
        } catch (final Throwable t) {
            throwUnchecked(t);
        }
    }

    private static <E extends RuntimeException> void throwUnchecked(Throwable t) {
        throw (E) t;
    }

    void tryRun() throws Throwable;
}

Prefiero esto new RuntimeException(t)porque tiene un seguimiento de pila más corto.

Ahora puedes hacer:

executorService.submit((ThrowingRunnable) () -> {throw new Exception()});

Descargo de responsabilidad: la capacidad de realizar conversiones no verificadas de esta manera podría eliminarse en versiones futuras de Java, cuando la información de tipo genérico se procesa no solo en tiempo de compilación, sino también en tiempo de ejecución.


0

Tu requisito no tiene ningún sentido. Si desea notificar al llamado del hilo sobre una excepción que ocurrió, puede hacerlo a través de un mecanismo de devolución de llamada. Esto puede ser a través de un Handler o una transmisión o cualquier otra cosa que se te ocurra.


0

Creo que un patrón de escucha podría ayudarte con este escenario. En caso de que ocurra una excepción en su run()método, use un bloque try-catch y en la captura envíe una notificación de un evento de excepción. Y luego maneje su evento de notificación. Creo que este sería un enfoque más limpio. Este enlace SO le brinda un puntero útil en esa dirección.


-1

La forma más sencilla es definir su propio objeto de excepción que extienda la RuntimeExceptionclase en lugar de la Exceptionclase.


¿Y cómo se hace con esta RuntimeException cuando se produce mi querido señor?
Lirón

Eso por sí solo no responde a la pregunta.
Karl Richter
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.