Mi pregunta es sobre InterruptedException
, que se lanza desde el Thread.sleep
método. Mientras se trabaja con ExecutorService
lo observado un comportamiento extraño que no entiendo; esto es lo que quiero decir:
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(() -> {
while(true)
{
//DO SOMETHING
Thread.sleep(5000);
}
});
Con este código, el compilador no me da ningún mensaje de error o que InterruptedException
de Thread.sleep
deben ser capturados. Pero cuando estoy tratando de cambiar la condición del bucle y reemplazar "verdadero" con alguna variable como esto:
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(() -> {
while(tasksObserving)
{
//DO SOMETHING
Thread.sleep(5000);
}
});
El compilador se queja constantemente de que InterruptedException
tiene que ser manejado. ¿Puede alguien explicarme por qué sucede esto, y por qué si la condición se establece en true el compilador ignora la InterruptedException?
La razón de esto, es que estas invocaciones son, de hecho, las invocaciones a dos métodos diferentes disponibles en sobrecargado ExecutorService
; cada uno de estos métodos que toman un único argumento de diferentes tipos:
<T> Future<T> submit(Callable<T> task);
Future<?> submit(Runnable task);
Entonces lo que sucede es que el compilador es la conversión de la lambda en el primer caso de su problema en una Callable<?>
interfaz funcional (invocando el método primera sobrecargada); y en el segundo caso de su problema convierte la lambda en una Runnable
interfaz funcional (invocando por lo tanto, el segundo método sobrecargado), requiriendo a causa de esto para manejar el Exception
lanzado; pero no en el caso anterior con el Callable
.
Aunque ambas interfaces funcionales no tienen ningún argumento, Callable<?>
devuelve un valor :
- exigible:
V call() throws Exception;
- ejecutable:
public abstract void run();
Si cambiamos a ejemplos que recortan el código para las piezas pertinentes (para investigar fácilmente sólo los bits curiosos), entonces podemos escribir, lo que es equivalente a los ejemplos originales:
ExecutorService executor = Executors.newSingleThreadExecutor();
// LAMBDA COMPILED INTO A 'Callable<?>'
executor.submit(() -> {
while (true)
throw new Exception();
});
// LAMBDA COMPILED INTO A 'Runnable': EXCEPTIONS MUST BE HANDLED BY LAMBDA ITSELF!
executor.submit(() -> {
boolean value = true;
while (value)
throw new Exception();
});
Con estos ejemplos, puede ser más fácil de observar que la razón por la cual el primero se convierte en una Callable<?>
, mientras que el segundo se convierte en una Runnable
es a causa de las inferencias del compilador .
En ambos casos, los cuerpos son lambda -vacío compatible , ya que cada instrucción de retorno en el bloque tiene la forma return;
.
Ahora, en el primer caso, el compilador hace lo siguiente:
- Detecta que todas las rutas de ejecución en el lanzamiento lambda declarar las excepciones comprobadas (de ahora en adelante nos referiremos como 'excepción' , lo que implica solamente 'comprueban excepciones' ). Esto incluye la invocación de cualquier método declarar excepciones que lanzan y la invocación explícita a
throw new <CHECKED_EXCEPTION>()
. - Concluye correctamente que el TODA cuerpo de la lambda es equivalente a un bloque de código que se declara excepciones de lanzamiento; que por supuesto DEBE ser: manipulados o re-lanzado.
- Dado que el lambda no está manejando la excepción, entonces los valores por defecto del compilador asumir que estos excepción (s) debe ser re-lanzado.
- Con seguridad infiere que este lambda debe coincidir con una interfaz funcional no puede
complete normally
y por lo tanto es compatible con valor . - Dado
Callable<?>
yRunnable
son posibles coincidencias de este lambda, el compilador selecciona la coincidencia más específica (para cubrir todos los escenarios); que es elCallable<?>
, la conversión de la lambda en una instancia del mismo y la creación de una referencia de invocación alsubmit(Callable<?>)
método sobrecargado.
Mientras que, en el segundo caso, el compilador hace lo siguiente:
- Detecta que puede haber rutas de ejecución de la lambda que NO declarar lanzar excepciones (en función de la lógica a-ser-evaluado ).
- Dado que no todos los caminos de ejecución declaran que lanzan excepciones, el compilador llega a la conclusión de que el cuerpo de la lambda es NO NECESARIAMENTE equivalente a un bloque de código que declara lanzar excepciones - compilador no le importa / prestar atención si algunas partes del código declaramos que puedan , sólo si todo el cuerpo hace o no.
- Con seguridad infiere que el lambda no es compatible con valor ; ya que MAY
complete normally
. - Selecciona
Runnable
(ya que es la única disponible apropiado interfaz funcional para el lambda para ser convertido en) y crea una referencia de invocación alsubmit(Runnable)
método sobrecargado. Todo esto viene al precio de delegar en el usuario, la responsabilidad de manejar cualquierException
s arrojado dondequiera que PUEDEN ocurrir dentro de las partes del cuerpo lambda.
Esta fue una gran pregunta - que tenía un montón de diversión persiguiendo abajo, gracias!