Tratamiento de excepciones en java

Cuando estamos programando, no todos los errores se producen cuando compilamos el código, sino que también se pueden producir errores en tiempo de ejecución. A estos errores que ocurren cuando ejecutamos el programa, se les conoce con el nombre de excepción. En este post, hablaré del tratamiento de excepciones en java.

Vamos a distinguir dos tipos de excepciones: las chequeadas y las no chequeadas. Por las primeras entendemos aquellas que manejamos en nuestro código con instrucciones que proporciona el lenguaje de programación, en el caso de Java serían los bloques try – cath. Y por excepciones no chequeadas entenderemos aquellas que no tratamos en nuestro código.

Excepciones no chequeadas

Las excepciones no chequeadas son errores de nuestro código que dependen de nosotros y debiéramos ser capaces de evitar empleando técnicas de programación segura. Un ejemplo clásico de este tipo de excepciones es la división por cero, que arroja un número infinito que el ordenador no puede tratar.

En la siguiente actividad, practicamos el manejo de las excepciones no chequeadas.

Actividad: Manejo de excepciones no chequeadas

  1. Empezamos abriendo un proyecto nuevo para practicar con las excepciones. En mi caso, le pongo de nombre al proyecto: PracticandoConExcepciones.
  2. Elimino los comentarios y me queda el código inicial:
  1. De momento nada nuevo. Vamos a crear ahora un código que nos genere un error de programación que se manifieste en tiempo de ejecución. Por ejemplo, la división por cero.
  2. Nuestro código sería:
  1. Con este código estamos realizando una división por cero que nos arrojaría la correspondiente excepción en ejecución. Mostrándonos un error. En este caso, NetBeans es capaz de evitarlo ya que este es un error tan habitual que ya lo tiene contemplado. Si ejecutamos el programa obtendremos:
  1. Se trata de un error tan básico que el compilador es capaz de tratarlo automáticamente. No obstante, es un error y nosotros no queremos que se produzca. El resultado infinito no nos vale para nada y no podemos operar con él.
  2. ¿Cómo prevenir este error?: Aplicando programación segura que en muchos casos se limita a comprobar los valores antes de operar con ellos.
  3. Pero mejor, veamos otro ejemplo muy habitual de excepción no chequeada que esta vez no solucione el compilador. Se trata del error: NullPointerException, intentamos leer el contenido de una variable que no contiene nada, que es nula. Por ejemplo, tecleamos el código siguiente:
  1. En este código intentamos acceder al String texto para, usando la función toUpperCase, pasar el texto a mayúsculas. Pero el texto es nulo, no tenemos nada. Si ejecutamos el programa, obtendremos el siguiente error:
  1. Donde vemos la excepción arrojada: NullPointerException. Para evitar esta excepción con prácticas de programación segura, sencillamente tendremos que comprobar antes que el String no es nulo. Haríamos algo parecido a:
  1. Si ejecutáramos ahora el programa, veremos que ya no se produce el error. Y a partir de este punto, podemos programar la respuesta para este caso. Por ejemplo, pedirle al usuario que introduzca un texto:
  1. Si ejecutamos ahora el programa, un posible resultado sería:
  1. Otra excepción no chequeada muy frecuente es: ArrayIndexOutOfBoundsException. Como su nombre indica, se produce cuando intentamos acceder a un elemento de un array empleando un índice que está fuera del rango. Por ejemplo si tenemos un array de 5 elementos e intentamos acceder al elemento array[5], nos daría esta excepción (recordemos que los índices de los arrays empiezan en 0, así que para un array de 5 elementos el rango de índices es 0-4)
  2. El alumno puede probar a reproducir esta excepción programando su propio código.

Excepciones chequeadas

Las excepciones chequeadas son aquellas que dependen del entorno y nosotros, como programadores, no tenemos manera de evitarlas. Lo que tenemos que hacer es asegurarnos de que las tratamos. Un ejemplo clásico de este tipo de excepciones es cuando el usuario nos tiene que proporcionar algún dato y lo hace de forma incorrecta. Por ejemplo, nos tiene que proporcionar la ruta a un fichero y lo hace mal, con lo que al intentar acceder al fichero no podremos y obtendríamos un error.

Para tratar estas excepciones, Java provee los bloques try – catch que el compilador nos va a obligar a utilizar cuando una operación tenga riesgo de arrojar una excepción, como por ejemplo abrir un fichero.

Las excepciones no chequeadas, que veíamos en el apartado anterior, también pueden ser tratadas en el código con un bloque try – catch, pero es más recomendable evitarlas con prácticas de programación segura en lugar de permitir que ocurran y tratarlas después.

Como siempre, practiquemos estos conceptos en una actividad.

Actividad: Manejo de excepciones chequeadas

  1. Continuamos trabajando en el proyecto: PracticandoConExcepciones.
  2. Para practicar las excepciones chequeadas, vamos a realizar un programa que cree un fichero y que introduzca una serie de datos en el mismo. Aunque todavía no hemos visto la clase File para el manejo de ficheros, aquí tendremos un pequeño adelanto para ver cómo tratar las excepciones con los bloques try-catch. Veremos también, que el compilador nos obliga a tratar este tipo de excepciones.
  3. Tecleamos el siguiente código:
  1. Nos hemos creado un objeto tipo File que en realidad es la ruta a nuestro fichero. Para crear el fichero empleamos la función createNewFile(). No hay que preocuparse de momento por el manejo de esta Clase, en el próximo cuaderno veremos el manejo de ficheros.
  2. De momento, lo que vemos es que el compilador nos indica el siguiente error:
  1. Nos fuerza a tratar la excepción, nos dice que tenemos que tratar la excepción que podría causarse al ejecutar esta instrucción.
  2. Esa excepción no es un error de programación. Puede deberse por ejemplo a que no exista el directorio donde queremos crear el fichero, un error de entorno imposible de evitar por programación, por lo que hay que asegurarnos de que los tratamos en nuestro código.
  3. Si clicamos en el icono que nos da el aviso de que tenemos que tratar la excepción, el compilador nos da algunas opciones. Vamos a escoger la opción:
  1. Y nuestro código quedaría:
  1. Podemos ver, que la sentencia que nos puede arrojar la excepción, en la línea 11, la hemos introducido dentro del bloque try. El código de la línea 13 lo introducimos nosotros para enseñar por pantalla la excepción que se produce.
  2. Así funciona el bloque try-catch. Colocamos en el bloque try las sentencias que queramos ejecutar que puedan causar una excepción. Y en el bloque catch colocamos las sentencias que emplearemos para tratar esa posible excepción
  3. El bloque catch recibe como parámetro la excepción que se ha producido. Sin embargo, ahora el programa no se interrumpirá arrojando un error, sino que se pasará a ejecutar las instrucciones en el bloque catch.
  4. Si ejecutamos este programa, obtenemos:
  1. Vemos que se produce una excepción que nos indica que la ruta donde hemos querido crear el fichero no se encuentra. Normal, es que no existe, no he creado la carpeta pruebas.
  2. Tratamos la excepción dentro de un bloque catch, escribiendo la misma por pantalla y el programa sigue su curso, ejecutando la línea 13. Pero el programa no ha fallado y ni se ha interrumpido delante del usuario, sino que hemos tratado la excepción y el programa ha finalizado de manera ordenada.

Para entender un poco mejor como funciona los bloques try-catch, vamos a introducir algunas líneas más en nuestro código. Dibujando mensajes por pantalla podremos entender mejor el flujo de nuestro programa. El código queda:

  1. Las nuevas líneas introducidas son sentencias simples para presentar por pantallas distintos mensajes que indican el momento en el que estamos en el programa.
  2. Las dos primeras líneas del bloque try:

Siempre se ejecutarán. Si se produjera una excepción en la segunda, el programa saltaría al bloque catch. Si tal excepción no se produce, continúa ejecutando las instrucciones en el bloque try y cuando acaba, sin que se produzcan excepciones, no ejecuta el bloque catch y continua con el código. En este caso, en la línea 18.

  1. Si ejecutamos el programa sin crear la carpeta pruebas, generaríamos la excepción y el resultado sería:
  1. Por el contrario, si creamos la carpeta, el resultado de la ejecución sería:

La clausula Finally

Viendo cómo funciona el bloque try-catch, podemos echar en falta una funcionalidad. No tenemos forma de hacer que un conjunto de instrucciones se ejecute siempre, con independencia de que se produzca la excepción o no. Podríamos duplicar el código en ambos bloques, try y catch, pero esto es bastante ineficiente.

Esta situación se da en no pocas ocasiones. Por ejemplo, trabajando con ficheros, debiéramos cerrar los objetos una vez utilizados, empleando el método close, se haya producido excepción o no. Si estamos intentando acceder a una base de datos, tendríamos básicamente la misma situación.

Para dar solución a este tema, java introduce un bloque adicional al try-catch, el bloque finally. Vamos a verlo en un ejemplo con un fichero.

Actividad: Entendiendo la clausula finally

  1. Continuamos trabajando en el proyecto: PracticandoConExcepciones. Recupero aquí el último código que tecleamos:
  1. Para probar el uso de finally, añadimos la cláusula con un mensaje. Aunque no tiene mucho sentido, nos ayuda a probar y entender el funcionamiento de finally. El código podría ser:
  1. Si ejecutamos este código, como no se produce excepción, el resultado sería:
  1. Lo siguiente que haremos será provocar una excepción para analizar el comportamiento de finally en este caso. Para ello, cambiamos el nombre de la carpeta donde íbamos a crear el fichero, poniendo un carpeta que no exista.
  2. Si ejecutamos el programa obtenemos el siguiente resultado:

En ambos casos, podemos ver que las instrucciones dentro del bloque finally se ejecutan siempre, haya o no excepción.

NOTA: Este post es parte de la colección “Java”. Puedes ver el índice de esta colección aquí.

Ejercicios con entradas y salidas

Última actualización: septiembre 24

En esta ocasión, propongo algunos ejercicios muy sencillos para practicar lo explicado sobre las entradas y salidas estándar: teclado y pantalla.

Ejercicio 1: Aplicar descuento

Codifica un programa que calcule el descuento de una compra. El programa pedirá al usuario que indique el precio del producto, el número de unidades que compra y el descuento total que tiene. Con esos datos, el programa presenta por pantalla lo que ha pagado el cliente y lo que se ha ahorrado gracias al descuento.

Una posible solución es:

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {

        double descuento, cantidad, precio;
        Scanner sc=new Scanner(System.in);
        System.out.println("Introduce el precio del producto");
        precio=sc.nextDouble();
        System.out.println("Indica cuantas unidades quieres comprar");
        cantidad=sc.nextDouble();
        System.out.println("Indica el descuento que te dan (0-100)");
        descuento=sc.nextDouble();
        System.out.println("Has comprado "+cantidad+" unidades con un descuento de "+descuento);
        System.out.println("Has pagado: "+(cantidad*precio*(1-descuento/100)));
        System.out.println("Te has ahorrado: "+(cantidad*precio*descuento/100));
    }
}

Ejercicio 2: Ecuación de segundo grado

Realiza un programa que devuelva los resultados de una ecuación de segundo grado. El usuario tiene que introducir por teclado los coeficientes de la ecuación de segundo grado. En el caso de no tener soluciones reales, hay que detectarlo e indicarlo con un mensaje por pantalla.

Recordatorio: para el polinomio:

las dos soluciones posibles vienen dadas por la ecuación:

Una posible solución sería:

import java.util.Scanner;

public class EcuacionSegundoGrado {

    public static void main(String[] args) {
        double a,b,c;
        double d, solucion1, solucion2;
        Scanner sc1=new Scanner(System.in);
        System.out.println("Introduce el coeficiente de segundo grado:");
        a=sc1.nextDouble();
        System.out.println("Introduce el coeficiente de primer grado:");
        b=sc1.nextDouble();
        System.out.println("Introduce el termino independiente:");
        c=sc1.nextDouble();
        d=b*b-4*a*c;
        if (d<0){
            System.out.println("No hay soluciones reales");
        }
        else {
            solucion1=-(b+Math.sqrt(d))/(2*a);
            solucion2=-(b-Math.sqrt(d))/(2*a);
            System.out.println("la primera solución es: "+solucion1);
            System.out.println("la segunda solución es: "+solucion2);
        }
    }
}

Ejercicio 3: Promedio de números introducidos

Realizar un programa que pida de manera interrumpida al usuario que introduzca un número entero positivo. El programa dejará de pedir al usuario que introduzca un número cuando el número introducido sea el cero. Al finalizar, el programa debe calcular y mostrar el promedio de los números introducidos.

import java.util.Scanner;

public class Main {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int numero;
        int suma = 0;
        int contador = 0;

        // Bucle para pedir números al usuario hasta que introduzca un 0
        while (true) {
            System.out.print("Introduce un número entero positivo (0 para terminar): ");
            numero = scanner.nextInt();
            if (numero == 0) {
                break; // Sale del bucle si el número introducido es 0
            } else if (numero > 0) {
                suma += numero;
                contador++;
            }
        }
        // Cálculo del promedio
        if (contador > 0) {
            double promedio = (double) suma / contador;
            System.out.println("El promedio de los números introducidos es: " + promedio);
        } else {
            System.out.println("No se introdujeron números positivos.");
        }
        // cierre del objeto Scanner
        scanner.close();
    }
}

NOTA: Este post es parte de la colección “Java”. Puedes ver el índice de esta colección aquí.

Ejercicios con listas en Java

Aquí tenéis una propuesta de ejercicios con listas en Java para practicar lo explicado en el post de listas. Hay varias soluciones posibles para resolver un mismo problema, unas más eficientes que otras. Aquí podéis ver una solución posible para cada ejercicio propuesto.

Ejercicio 1: ListaNumerosDivisiblesPorNum

Enunciado:

Crea una lista de todos los números del 1 a 100 que sean divisibles por un número que introduzca el usuario. Este número tiene que estar comprendido entre 2 y 7. Pinta la lista por pantalla.

Sigue leyendo Ejercicios con listas en Java

La colección Mapas en Java

Otro tipo de colección muy interesante son los Mapas. Los mapas son colecciones de parejas clave-valor. Podría decirse que son como los arrays pero en lugar de usar un índice consecutivo que marca la posición del elemento en el array, vamos a utilizar una clave asociada a cada elemento.

La clase HashMap

La clase HashMap tiene la siguiente sintaxis:

HashMap<tipo clave,tipo de elemento>nombreDelMap= new HashMap<>();

Para trabajar con mapas vamos a utilizar la clase HashMap. Practiquemos con ella en la siguiente actividad:

Sigue leyendo La colección Mapas en Java

Listas en Java: tipo Set

En el post de Colecciones, mencionaba dos tipos de listas con las que podemos trabajar en Java: List y Set. En un post anterior me centraba en las listas de tipo List y ahora lo haré en las tipo Set, que a diferencia de las anteriores, no admiten duplicados.

La clase HashSet

Las listas tipo Set, las manejaremos con la clase HashSet. Esta clase funciona de manera muy parecida a la clase ArrayList, tienen muchos métodos iguales. Por ello, en la actividad siguiente para practicar con esta clase, me centraré en las diferencias de la clase HashSet con la clase ArrayList, vista previamente.

Sigue leyendo Listas en Java: tipo Set

Listas en Java: tipo List

Cuando en el post de Colecciones, hablaba de las listas en Java, mencionaba dos tipos de listas: List y Set. Ahora vamos a centrarnos en las listas de tipo List que manejaremos con la clase ArrayList.

La clase ArrayList

La sintaxis de la declaración de una de estas lista con la clase ArrayList es la siguiente:

ArrayList<tipo de elemento> nombreDeLaLista = new ArrayList< > ;

Vamos a verlo con un ejemplo práctico.

Sigue leyendo Listas en Java: tipo List

Colecciones en Java

En un post anterior habíamos visto los Arrays que nos obligaban a manejar un número fijo de elementos. Se inicializan con una dimensión fija que luego no podemos modificar durante la ejecución del programa.

Java proporciona otro tipo de Clases más flexibles y con ventajas sobre los arrays que son las Colecciones. Con estas vamos a poder trabajar, abstrayéndonos del índice y manejando un número de elementos variable durante la ejecución del programa.

Hay varias clases interesantes para trabajar con colecciones y todas extienden de la clase padre Collection.

Sigue leyendo Colecciones en Java

Ejercicios con Clases en Java

Última actualización: octubre 2024

Aquí te dejo varios ejercicios con Clases en Java, para que puedas practicar lo aprendido en post anteriores. No explico la solución, sencillamente muestro una posible solución, pero mi recomendación es que intentes hacer los ejercicios sin mirar mi solución, y que la consultes sólo si te atascas.

Ejercicio 1: La clase Pais

Crea una clase llamada Pais.

La clase debe tener los siguientes parámetros (recuerda que los parámetros deben ser declarados como privados por cuestiones de seguridad):

Sigue leyendo Ejercicios con Clases en Java

El Polimorfismo

Última actualización: julio 24

El polimorfismo hace referencia a la capacidad de que objetos de diferentes clases, respondan de manera diferente a un mismo método. En Java, esto se consigue mediante la herencia y las interfaces, posibilitando que distintas clases implementen un mismo método de formas distintas, ajustándolo a sus necesidades.

En el polimorfismo por herencia, las distintas subclases implementan un método de la superclase de distintas maneras, ajustándolo a las necesidades específicas de la subclase en cuestión.

Actividad: Practicando con el polimorfismo

  1. Abrimos un nuevo proyecto de Java en nuestro IDE de trabajo.
  2. Primero definimos la superclase: Animal
class Animal{
    public void comer() {
        System.out.println("El animal se alimenta");
    }
}
Sigue leyendo El Polimorfismo

Las Clases abstractas y los interfaces

Última actualización: julio 24

Las Clases abstractas y los interfaces son mecanismos que nos ayudan a definir estructuras abstractas, que son especificadas en las clases que las implementan de acuerdo a sus necesidades. Esto habilita al programador de Java a definir un grado de abstracción en sus desarrollos, posibilitando una programación más estructurada y modular, favoreciendo la reutilización del código.

Clases abstractas

Las Clases abstractas nos permiten un mayor nivel de abstracción al definir la estructura de los objetos de nuestro programa. Podemos indicar solamente que comportamiento hay que definir, pero no indicar como. En la práctica esto se traduce en declarar un método, pero no implementarlo. Esto lo indicamos empleando el modificador abstract, que se incluirá tanto en la firma del método como de la Clase que lo contiene.

Sigue leyendo Las Clases abstractas y los interfaces