Los errores en Java provienen de un tipo de clase especial llamada Throwable
esta clase es la base de la cual se extienden las excepciones Exception
y errores Error
, una excepción puede ser “lanzada” (throw
), por parte de nuestro codigo, explicita o implicitamente.
Ej.
// implicitamente
throw new Exception("Hubo un problema, :(")
// explicitamente
public void method() throws IOException {
throw new IOException("Error")
}
method() // Lanzara una excepción al usarlo.
Grafico de jerarquía de excepciones
Loading graph...
La clase Throwable
es la superclase de todas las excepciones y errores en Java. Sólo los objetos decendientes de esta clase pueden ser “lanzados” o ser utilizados en conjunto con la keyword throw
.
Tipos de excepciones
Existen dos tipos de excepciones, las verificadas (checked) y las no verificadas (unchecked), para entender su diferencia citare a lo que oracle tiene que decir al respecto.
”Si un cliente puede recuperarse razonablenmente de una excepción, utiliza una excepción verificada. Si el cliente no puede hacer nada para recuperarse de la excepción, utiliza una excepción no verificada ” (Oracle docs)
La verdad al leer esto, no le encontré mucho sentido, yo lo entiendo más de esta forma.
”Excepción verificada: el compilador te obligará a manejarla”
Excepción no verificada: al compilador no le importará si la manejas o no.”
(Matías Fuentes, 2024)
Excepciones verificadas (Checked exceptions)
Supongamos que tenemos un metódo que accede a un archivo.
public static void main(String[] args) {
readFile("example.txt");
}
public static void readFile(String path) { // El compilador nos mostrará un error, necesitamos manejar la excepcion FileNotFoundException!
FileReader file = new FileReader(path);
}
La clase FileReader
tiene de forma explicita las excepciones que puede “lanzar”, por lo tanto, el compilador de Java, nos obligará a manejar esta excepción para evitar errores en el programa. Si esta excepción no se maneja el programa no compila y por ende, no se puede ejecutar.
La firma de la clase FileReader
debería tener implementado algo así en su constructor. Ej.
class FileReader extends InputStreamReader {
public FileReader(String path) throws FileNotFoundException {
// ...
}
//...
}
Por lo tanto, como en la firma del constructor de FileReader
explicitamente muestra que puede lanzar una excepción el compilador puede verificar que la excepción sea manejada en tiempo de compilación, indicando el error al usuario.
Recordar que todas las excepciones verificadas extienden a Exception
, por lo tanto FileNotFoundException
extiende a Exception
Excepciones no verificadas (Unchecked exceptions)
Supongamos que queremos dividir por cero.
public int division(int a, int b) {
return a / b;
}
En el ejemplo anterior, es probable que el metódo pueda lanzar una excepción, ya que puede darse el caso b == 0
, en donde se estaría dividiendo por cero y esto es matemáticamente imposible. Cabe destacar que en ninguna parte se nos obliga a manejar la excepción ya que es una excepción no verificada.
La excepción que arrojaría este metódo sería ArithmeticException
y entregaría un mensaje mencionando el problema, en este caso, / by zero
.
Pero ¿Porqué es una excepción no verificada?, Porque el compilador no tiene como saber que tipo de excepción puede ser “lanzada”, al estar de forma implicita.
Recordar que todas las excepciones no verificadas extienden a RuntimeException
, por lo tanto ArithmenticException
extiende a RuntimeException
Delegando el manejo de excepciones
En el caso de que no quisieramos manejar las excepciones que el metódo o objeto pueda “lanzar”, podemos delegar el manejo de las excepciones al cliente, esto se logra, utilizando la keyword throws
en la firma del metódo. Ya visto en ejemplos anteriores. throws
indica las posibles excepciones que pueden ocurrir, estas siempre serán excepciones verificadas (checked exceptions), ya que si se utiliza una excepción no verificada junto con throws
el compilador lo ignorará de todas formas.
class FileReader extends InputStreamReader {
public FileReader(String path) throws FileNotFoundException { // <-- Aquí decidimos pasar esa resposabilidad al cliente, es decir, quién va a utilizar el metódo.
super(new FileInputStream(fileName)); // <-- esto puede lanzar la excepción `FileNotFoundException`
}
//...
}
Esto quiere decir que cuando utilizemos este metódo o objeto, deberemos manejar sí o sí la excepción, si no, el programa no compilará.
public static void main(String[] args) {
readFile("ejemplo.txt");
}
public static void readFile(String path) {
try {
FileReader file = new FileReader(path);
} catch (FileNotFoundException fnfe) {
System.out.println("Archivo no encontrado");
}
}
Ahora al ser clientes de la clase FileReader
deberemos manejar la excepción y evitar así el error de compilación.
Errores
Los errores no son muy utilizados en los programas en Java, ya que, estos son de bajo nivel, es decir, que son problemas que el programa no puede manejar. Los errores por lo general son limitantes más de hardware o arquitectura del lenguaje. Algunos errores por ejemplo:
StackOverFlowError
: El programa se a quedado sin memoria para almacenar más llamadas a funciones.OutOfMemoryError
: El programa se ha quedado sin memoria para almacenar objetos.
Como puedes observar en las descripciones de los errores, implican un problema más cercano al hardware, que no podríamos manejar en nuestro programa.
Conclusión
Las excepciones son una herramienta para manejar errores y describirlos. Las excepciones más estrictas son las verificadas y las menos estrictas son las no verificadas. Los errores son para indicar un problema de más bajo nivel (hardware), que nuestro programa no podría manejar. El manejo de errores se puede delegar a los usuarios del metódo o clase.