1. Introducción
Hola a todos y bienvenido al primer post de nuestro blog en el 2018!. Hace unas cuantas semanas me registré en la plataforma Codewars. Esta es una plataforma que te permite entrenar tu capacidad de resolución de problemas algorítmicos a través de distintos lenguajes de programación. Los problemas que se van presentando van acorde a la categoría que el usuario tiene, pudiendo promocionando de nivel si se resuelven un conjunto de problemas de una determinada dificultad.
Pues bien, en uno de estos problemas se nos presentaba el siguiente enunciado. (PD: Los nombres de las clases han sido cambiadas para que este POST no pueda ser encontrado con facilidad por aquellos a los que se le presenta el problema desde Codewars.)
Ayuda al preso a salir de la carcel teniendo en cuenta que éste tiene la forma
El código que hay que implementar es el siguiente
Teniendo en cuenta que hay que pasar el siguiente test
import org.junit.Test; public class JailTest { @Test(expected = Prisioner.class) public void test(){ JailTest.escape(); } }
2. Resolución
El problema consiste en emitir una excepción checked sin tener la capacidad de poder cambiar la firma del método. De lo contrario la solución sería la siguiente.
Modificamos la firma del método escape y test de las clases Jail y JailTest respectivamente.
import org.junit.Test; public class JailTest { @Test(expected = Prisioner.class) public void test() throws Prisioner{ JailTest.escape(); } }
Sin embargo, como decimos, cambiar la firma de los métodos no está permitido en los ejercicios de Codewars por lo que esta solución se debe dercartar. A continuación tuve la impresión que el problema se resolvería extendiendo la clase Prisioner, sin embargo esta opción se tiene que descartar también ya que la clase contiene el modificador final en su definición. Por tanto a partir de aquí se me ocurrieron otras dos estrategias a seguir. Intentar crear un Wrapper de la excepción checked y lanzar ésta o intentar resolver el problema por reflexión. Tras media hora de fracasos continuados me di por vencido y decidí desvelar la solución de otros usuarios.
La solución se basa en un truco del compilador de Java y es el siguiente
public class Jail { public static void escape() { Jail.<RuntimeException>doEscape(); } private static <T extends Throwable> void doEscape() throws T{ throw (T)new Prisioner(); } }
Lo que se hace es lanzar la excepción dentro de un método que admite un tipo genérico indicando el tipo de excepción que se va a emitir en la invocación del método. Esto se consigue con la siguiente parte de la firma del método doEscape, throws T
. Lo único que tiene que hacer el método, por tanto, es lanzar la excepción Prisioner haciendo un cast previo a T. Al ver la solución no me creía que pudiese funcionar ya que tendría que emitirse un ClassCastExcepction al intentar hacer un cast entre Prisioner y RuntimeException. Pues NO!, debe ser que esto es un truco de la JVM para poder convertir excepciones checked en unchecked.