Excepciones
Introducción
Las excepciones en Java son errores que se producen en tiempo de ejecución. Cuando realizamos el proceso de compilación pueden aparecer una serie de errores que deben ser corregidos para que el programa pueda compilarse y generarse. Más tarde, durante la ejecución del mismo, pueden producirse una serie de errores, los cuales son imposibles de detectar durante la fase de compilación. Son este tipo de errores los que, en Java, acaban lanzando una excepción si llegan a producirse:
Estamos escribiendo en disco y éste se llena (IOException ) Estamos descargando un fichero y cae la conexión a Internet (ConnectionException ) Estamos recorriendo un array en un bucle y accedemos a una posición que no existe (IndexOutOfBoundException ) Accedemos atributos o métodos de un objeto cuyo valor es nulo (NullPointerException ) Realizamos una operación matemática no válida (división por cero) (ArithmeticException )
En Java se propone el control de excepciones para evitar que se produzcan estos errores en lugar de tener que escribir continuamente estructuras if que controlen que no ocurre nada anómalo que impida la ejecución de un cierto código. Así, lo que haremos será colocar dentro un bloque controlado todo el código (y el que dependa de éste) que sea susceptible de producir una excepción, sin interrumpir el flujo de nuestro programa (al contrario de lo que ocurre añadiendo sentencias if ).
Palabras reservadas clave: try, catch, exception, throws, finally
Uso de try catch
Por tanto podemos predecir que tipo de errores/excepciones tendrá nuestro programa. Y Java nos aporta el “try catch” para “capturarlas”. Es decir capturar y tratar dichas excepciones para que no se rompa el programa. Crear programas más robustos con control de errores. Ejemplo:
class App {
public static void main(String[] args) {
int pesoPaquete;
try{
System.out.println("antes de que se genere la excepción");
pesoPaquete=10/0;
System.out.println("esto jamás se imprime");
}catch(ArithmeticException miExcepcion){
System.out.println("muy, muy, mu mal: no se puede dividir por cero");
}
}
}
En el anterior ejemplo observamos que el try es el que va a definir el espacio donde esperamos que surga la excepción. Si se genera dicha excepcion el catch determina lo que se va a ejecutar. El try y el catch son inseparables, y dentro de los paréntisis del catch creamos una variable (o varias) que referenciará la excepción. También no puede haber instruciones entre el try y el catch.
Un try y varios catch
En un mismo bloque de sentencias nos puede interesar capturar diferentes excepciones:
class App {
public static void main(String[] args) {
int pesoPaquete=10;
int divisor=0;
try{
System.out.println("antes de que se genere la excepción");
pesoPaquete=pesoPaquete/divisor;
pesoPaquete= Integer.parseInt("10.5");
System.out.println("esto jamás se imprime");
}catch(ArithmeticException miExcepcion){
System.out.println("muy, muy, muy, mal no se puede dividir por cero");
}catch(NumberFormatException miExcepcion){
System.out.println("imposible convertir en entero ése string");
}
System.out.println("el programa sigue su ejecución se recupero de la excepción ...");
}
}
Una formas más compacta:
class App {
public static void main(String[] args) {
int pesoPaquete=10;
int divisor=0;
try{
System.out.println("antes de que se genere la excepción");
pesoPaquete=pesoPaquete/divisor;
pesoPaquete= Integer.parseInt("10.5");
System.out.println("esto jamás se imprime");
}catch(ArithmeticException|NumberFormatException miExcepcion){
System.out.println("mi código es excepcional....");
}
System.out.println("el programa sigue su ejecución se recupero de la excepción ...");
}
}
Ejercicio 1
Si al trabajar con un array x de tamaño n, el último elemento es x[n-1]. Si sobrepasamos los límites del array por ejemplo intentando usar x[n], java genera una excepción. Reforma el código para que capture la excepción y el programa no rompa.
class App{
public static void main(String[] args){
int[] x= {0,1,2,3,4};
x[5]=5;
System.out.println("El programa se recupera de la excepción y continua");
}
}
Ejercicio 2
Queremos calcular el factorial de un número pero asegurándonos que el usuario introduce un entero por teclado. Hasta que no introduzca un entero le estaremos pidiendo repetitivamente que introduzca el entero. Investiga InputMismatchException y NumberFormatException.
class App{
public static void main(String[] args) {
Scanner teclado= new Scanner(System.in);
int n=0; //Numero entero introducido por teclado.
//añadir código para obtener número entero correcto usando mecanismo excepciones
//calculamos factorial
int factorial=n;
String salida=n+"! = "+n;
for(int i=n-1;i>0;i--){
salida+="*"+i;
factorial*=i;
}
salida+=" = "+factorial;
System.out.println(salida);
}
}
Uso del throws
Como estáis observando en Java todas las excepciones son clase y derivan de Throwable, cuando se genera una excepción se crea un objeto de alguna de ellas. La clase padre de las excepciones es Throwable y las subclases:
- Error: se corresponden con errores serios de la JVM y que nuestra aplicación no capturará (OutOfMemoryError,…).
- Exception: se corresponden con errores debidos a la propia actividad del programa (ArithmeticException, NullPointerException,…) y serán habitualmente capturadas.
A veces nos interesa propagar una excepción a métodos o clases superiores o también generar una excepción. Para ello tenemos el throws o el throw:
public class DemoEx5 {
public static int dameNum() throws NumberFormatException {
java.util.Scanner cin = new java.util.Scanner(System.in);
return Integer.parseInt(cin.nextLine().trim());
}
public static void main(String[] args) {
System.out.println("Dame un número: ");
try {
System.out.println(dameNum());
}
catch(Exception e) {
System.out.println("Excepción recibida --> " + e);
}
}
}
public class DemoEx4 {
public static int dameNum() {
java.util.Scanner cin = new java.util.Scanner(System.in);
int num;
try {
num = Integer.parseInt(cin.nextLine().trim());
}
catch(NumberFormatException e) {
System.out.println("Entrada no válida. Relanzando excepción...");
throw e;
}
return num;
}
public static void main(String[] args) {
System.out.println("Dame un número: ");
try {
System.out.println(dameNum());
}
catch(Exception e) {
System.out.println("Excepción recibida --> " + e);
}
}
public class TestThrow1 {
public static void validate(int age) {
if(age<18) {
throw new ArithmeticException("Person is not eligible to vote");
}
else {
System.out.println("Person is eligible to vote!!");
}
}
public static void main(String args[]){
validate(13);
System.out.println("rest of the code...");
}
}
Ejercicio 3
Partiendo del código fuente siguiente añádele la capacidad de generar una excepción cuando se genere una figura de color blanco de forma que funcione el siguiente main.
class App {
public static void main(String[] args) {
try {
Circulo c = new Circulo(2.0, "blanco");
System.out.println("Area circulo " + c.area());
} catch (Exception e) {
System.out.println("NO SE PUDO CREAR OBJETO: " + e.getMessage());
}
try {
Triangulo t = new Triangulo(2.0, 3.0, "rojo");
System.out.println("Area triangulo " + t.area());
} catch (Exception e) {
System.out.println("NO SE PUDO CREAR OBJETO" + e.getMessage());
}
}
}
abstract class Figura {
protected String color;
public Figura(String color) {
this.color = color;
}
abstract public double area();
}
class Triangulo extends Figura {
private double base;
private double altura;
public Triangulo(double base, double altura, String color) {
super(color);
this.base = base;
this.altura = altura;
}
@Override
public double area() {
return base * altura / 2;
}
}
class Circulo extends Figura {
private double radio;
public Circulo(double radio, String color) {
super(color);
this.radio = radio;
}
@Override
public double area() {
return Math.PI * radio * radio;
}
}
Uso del finally
Existe un tercer bloque de instrucciones cuando usamos try-catch, se llama finally. En este bloque incluiremos las instrucciones que siempre queremos ejecutar al finalizar el try, haya o no excepciones. Y ante cualquier situación de error o exit siempre se ejecuta. Ejemplo:
class UsoFinally{
static void generarExcepcion(int paraSwitch){
int t=10;
System.out.println("Recibiendo " + paraSwitch);
try{
switch(paraSwitch){
case 0:
t=10/paraSwitch;//forzamos division por zero
break;
case 1:
t=Integer.parseInt("10.5");//provocamos error de formato
break;
case 2:
return;
}
}catch(ArithmeticException exc){
System.out.println("no se puede dividir por cero ¡animal!");
return;
}catch(NumberFormatException miExcepcion){
System.out.println("imposible convertir en entero ese string");
}finally{
System.out.println("dejando try");
System.out.println();
}
}
}
class App {
public static void main(String[] args) {
for(int i=0;i<3;i++){
UsoFinally.generarExcepcion(i);
}
}
}
Este bloque es muy práctico para asegurarnos cierres de conexión, liberación de recursos etc…
Ejercicio 4
Escribe de nuevo el siguiente código, respetándose tal cual, pero añadiendo la capacidad de trabajar con excepciones (clase Exception) de forma que cuando se crea un rectángulo cuyo origen es un punto con alguna coordenada negativa se lanza una Exception.
class Punto {
int x = 0;
int y = 0;
Punto(int x, int y) {
this.x = x;
this.y = y;
}
}
class Rectangulo {
Punto origen;
int ancho;
int alto;
Rectangulo(int x, int y, int w, int h) {
origen = new Punto(x,y);
ancho = w;
alto = h;
}
}
class App{
public static void main(String[] args){
Rectangulo miRectangulo=new Rectangulo(-2,3,4,5);
}
}
Jerarquía
Crear tu propias excepciones
En Java a parte de terner las excepciones “oficiales”, también podemos implementar nuestras propias excepciones “a medida”. Y nos aporta un mayor grado de flexibilidad y libertad. A través de la creación y uso de estas excepciones propias, podremos manejar errores que sean específicos de nuestra aplicación. Estas nuevas excepciones serán gestionadas empleando los mismos mecanismos proporcionados por Java para capturar, procesar y relanzar cualquiera de las excepciones predefinidas en el lenguaje. Por convenio el nombre de la nueva clase para tu excepción debe terminar en “Exception”.
La forma es usar herencia con la clase Exception, y podemos usar estos métodos con sobreescritura:
- getMessage()
- printStackTrace()
- toString()
public class DatoNoValidoException extends Exception {
int errCode;
String valor;
public DatoNoValidoException(String valor, int errCode, String msg) {
super(msg);
this.valor = valor;
this.errCode = errCode;
}
public int getErrorCode() {
return this.errCode;
}
public String toString() {
return "[ERR: " + this.errCode + "] " +
this.getMessage() + " (value: " + this.valor + ")";
}
}
public class DemoEx6 {
public static void validaEdad(int edad) throws DatoNoValidoException {
if(edad < 18)
throw new DatoNoValidoException(Integer.toString(edad), 101, "Menor de edad");
}
public static void main(String[] args) {
try {
validaEdad(15);
}
catch(Exception e) {
System.out.println("Excepción recibida --> " + e);
}
}
}
Ejercicio 5
Tenemos un pequeño programa que controla el stock de un producto (id, precio y cantidad). La cantidad por defecto es 50 unidades. Crea un método de vender producto (puede ser estático), y crea tu propia clase de excepción de falta de stock.
Ejercicio 6
Amplia el ejercicio de Ordenadores del inicio del curso:
-
Añadir a la clase Ordenador el atributo: private String numeroDeSerie;
-
El constructor de Ordenador también se encarga de lanzar una excepción de tipo OrdenadorException cuando se intenta crear un Ordenador en las siguientes situaciones:
- Se intenta configurar un ordenador con procesador modelo I7 sin disco tipo SATA3
- El número de serie comienza por HP y tiene menos de 4gb de ram. (Busca en API método de clase String que devuelve comienzo de String)
-
Un objeto OrdenadorException recibe en su constructor un número de serie y un mensaje y reescribe con esta info getMessage()
-
Funciona el siguiente main:
//Principal.java
import ordenador.Ordenador;
import ordenador.OrdenadorException;
class Principal{
public static void main(String[] args) {
//formato contructor Ordenador:
//NumeroDeSerie,capacidadMemoria,tipMemoria,velocidadMemoria,capacidadDisco,tipoDisco,velocidadDisco,tipoProcesador,velocidadProcesador,precio
try{
new Ordenador("DELL122",8,"DDR2",533,(float)2.0,"SATA",7200,"i7",(float)3.3,400);
System.out.println("ORDENADOR DELL122 OK");
}catch(OrdenadorException e){
System.out.println(e.getMessage());
}
try{
new Ordenador("CLONIC900",8,"DDR2",533,(float)2.0,"SATA3",7200,"i7",(float)3.3,400);
System.out.println("ORDENADOR CLONIC900 OK");
}catch(OrdenadorException e){
System.out.println(e.getMessage());
}
try{
new Ordenador("HP511",2,"DDR2",533,(float)2.0,"SATA",7200,"i5",(float)3.3,400);
System.out.println("ORDENADOR HP511 OK");
}catch(OrdenadorException e){
System.out.println(e.getMessage());
}
}
}
Que genera la salida: DELL122: I7 sin SATA3 no se monta ORDENADOR CLONIC900 OK HP511: Serie HP no puede tener menos de 4gb de memoria