En PC Resumen hablaremos del funcionamiento que tiene la sentencia finally en Java dentro de las excepciones. Cuando se tiran excepciones, la ejecución en un método sigue un camino no lineal que altera el flujo normal. Dependiendo de la forma en que se haya codificar el método, incluso es posible que una excepción provoque que el método finalice de forma prematura. Esto puede suponer un problema. La palabra clave finally está diseñada para resolver estos problemas.

La cláusula finally crea un bloque de código que se ejecutará después de que se haya completado el un bloque try / catch y antes de que se ejecute el código que sigue a este bloque. El bloque finally se ejecutará tanto si se tira la excepción como sino. Esto puede ser útil para cerrar archivos o liberar cualquier recurso que se hubiera asignado al principio de un método. Los archivos, las conexiones de bases de datos y las conexiones de red que no se cierren apropiadamente podrían no estar disponibles para su uso en otros programas.

La cláusula finally es opcional, pero cada sentencia try necesita como mínimo, una cláusula catch o finally.

Java garantiza que un bloque finally (si los hay) se ejecutará, se tire o no una excepción en el bloque try correspondiente, donde en cualquiera de sus bloques catch.

Como un bloque finally siempre se ejecuta, normalmente contiene código para "liberar recursos".

En PC Resumen vamos a hablar del funcionamiento de la sentencia throws en Java. Si un método es capaz de causar una excepción que él mismo no puede manejar, debe especificarse el comportamiento de forma que los métodos que llamen a este método puedan protegerse de la excepción. Para hacerlo debe incluir una cláusula throws a la declaración del método. Esta cláusula da un listado de los tipos de excepciones que puede tirar. Si no se declara se provoca un error en tiempo de compilación. La lista de excepciones debe ir separada por comas.

Fijémonos en el siguiente código:

import java.util.InputMismatchException;
import java.util.Scanner;

class DivisionEntreZeroAmbtractamentExcepcions {
    public static int cociente(int numerador, int denominador) throws ArithmeticException {
        return numerador / denominador; // posible división por cero
    }
    
    public static void main(String args[]) {
        Scanner explorador = new Scanner(System.in); // objeto Scanner para la entrada
        boolean continuar = true; // determina si se necesitan más datos de entrada
        do {
            try {
                System.out.print("Introduce un numerador entero: ");
                int numerador = explorador.nextInt();
                System.out.print ("Introduce un denominador entero: ");
                int denominador = explorador.nextInt();
                int resultado = cociente(numerador, denominador);
                System.out.printf ("\nResultado: %d / %d =% d\n", numerador, denominador, resultado);
                continuar = false; // entrada exitosa;
            } catch (InputMismatchException inputMismatchException) {
                System.err.printf("\nExcepción: %s \n",
                inputMismatchException);
                explorador.nextLine(); // descarta entrada para probar de nuevo
                System.out.println ("Introduce enteros. \n");
            } // fin de bloque catch
            catch (ArithmeticException arithmeticException) {
                System.err.printf ("\nExcepción: %s \n", arithmeticException);
                System.out.println ("Zero no es un denominador válido. \n");
            }
        } While (continuar);
    }
}

En el método cociente ponemos throws ArithmeticException. Esta cláusula especifica la excepción que tira el método. La cláusula aparece después de la lista de parámetros del método y antes de su cuerpo.

Un método puede lanzar excepciones de las clases que se listen en la cláusula throws o en la de sus subclases. Hemos puesto esta cláusula para indicar al resto del programa que este método puede lanzar una excepción ArithmeticException. De este modo los usuarios de este método se les informa que este método puede lanzar la excepción.

Se debe entender que cuando cociente tira la excepción el método termina, no devuelve nada y las variables locales quedan fuera del alcance y se destruyen. Si este método contiene variables locales que sean referencias a objetos y no hay otras referencias a estos objetos, estos se marcan para la recolección de basura.

  • Sabiendo que un método podrá tirar una excepción, es recomendable incluir el código apropiado para manejar las excepciones en nuestro programa, para que sea más robusto.
  • Hay que leer la documentación de la API para saber cómo funciona un método antes de usarlo. La documentación nos informará de las posibles excepciones que tira un método y también indica las razones por las que pueden ocurrir estas excepciones. Después hay que incluir el código adecuado para manejarlas.

En PC Resumen hablaremos de las formas en que se puede utilizar las sentencias try y catch en Java. A pesar de que el gestor de excepciones que proporciona Java es útil para la depuración, generalmente el programador prefiere manejar la excepción él mismo. Esto aporta dos ventajas. En primer lugar, permite corregir el error. En segundo lugar, evita que el programa concluya de forma automática.

Para evitar esta situación y gestionar el error en tiempo de ejecución, sencillamente se debe incluir el código que queramos controlar dentro de un bloque try. Inmediatamente después del bloque try, se incluye la cláusula catch que especifica el tipo de excepción que se desea capturar.

Veamos ahora un ejemplo sencillo para capturar la excepción ArithmeticException generada por la división por cero.

class Exc2 {
    public static void main (String args[]) {
        int d, a;
        try { // Controla un bloque de código.
            d = 0;
            a = 42 / d;
            System.out.println("Esto no se imprimirá.");
        } Catch (ArithmeticException e) { // Captura el error
            System.out.println("División por cero !!");
        }
        System.out.println("Después del catch");
    }
}

Este programa generaría la siguiente salida:

División por cero !!
Después del catch

Tenga en cuenta que la llamada a println dentro del try no llegará a ejecutarse. Una vez generada una excepción, el control del programa pasa al bloque catch. Una vez finalizado este bloque, el programa continúa.

Un bloque try y su correspondiente catch forman una unidad. El campo de acción de una cláusula catch se limita a aquellas sentencias especificadas por la sentencia try que la precede inmediatamente. Las sentencias protegidas por try deben estar entre llaves, es decir deben estar en un bloque.

El objetivo de la mayoría de cláusulas catch bien construidas debe ser el de resolver la condición excepcional y luego continuar como si no hubiera pasado nada.

La clase Throwable son descendientes todas las excepciones, sobreescriben el método toString, definido por Object y devuelve una cadena que contiene la descripción de la excepción. La descripción la podemos imprimir, pasando simplemente la excepción como parámetro.

Por ejemplo:

catch (ArithmeticException e) {
    System.out.println("Excepción:" + e);
}

Este código en el caso del programa anterior sacaría por pantalla:

Excepción: java.lang.ArithmeticException: /by zero

Aunque en este caso el mensaje no tiene mucho interés, la posibilidad de presentar la descripción de la excepción es de un gran valor, sobre todo cuando se está experimentando con las excepciones o se está depurando un programa.

Cláusulas catch múltiples

En ciertos casos, una única secuencia de código, puede originar más de un tipo de excepción. Para controlar estas situaciones, pueden especificarse más de una cláusula catch, para que cada una de ellas capture un tipo diferente de excepción. Cuando se lanza una excepción, se inspecciona cada sentencia catch, y se ejecuta la primera que coincida con la excepción. Una vez ejecutada esta es continúa después del bloque try / catch.

Por ejemplo:

class MultiCatch {
    public static void main (String args[]) {
        try {
            int a = args.length;
            System.out.println("a = " + a);
            int b = 42 / a;
            int c[] = {1};
            c[42] = 99;
        } Catch (ArithmeticException e) {
            System.out.println("División por 0: " + e);
        } Catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("Te has salido del array " + e);
        }
        System.out.println("Después de los bloques try / catch");
    }
}

Desde el JDK 1.8, también se puede estructurar de la siguiente manera la cláusula catch cuando es necesario controlar varias excepciones:

class MultiCatch {
    public static void main (String args[]) {
        try {
            int a = args.length;
            System.out.println("a = " + a);
            int b = 42 / a;
            int c[] = {1};
            c[42] = 99;
        } Catch (ArithmeticException | ArrayIndexOutOfBoundsException e) {
            System.out.println(e.getMessage());
        }
        System.out.println("Después de los bloques try / catch");
    }
}

Este programa provocará una excepción de división por cero si se ejecuta sin parámetros en la línea de comandos. Pero sino aparecerá una excepción de tipo ArrayIndexOutOfBoundsException ya que intentamos acceder a la posición 42 de una mesa de tamaño 1.

Si utilizamos varias sentencias catch, hay que tener claro que las subclases de excepción deben ir ante cualquiera de sus superclases. Esto es así porque la sentencia catch que utiliza una superclase, capturará excepciones de este tipo más cualquiera de sus subclases y por lo tanto éstas no se ejecutarán si están tras la superclase. Además, en Java se produce error si hay un código inalcanzable.

Sentencias try anidadas

Las sentencias try pueden estar dentro del bloque de otra sentencia try. Cada vez que se introduce una sentencia try el contexto de esta excepción también se almacena en la pila. Si una sentencia try no tiene su correspondiente gestor catch para una excepción determinada, se examina la pila buscando los gestores catch de las siguientes sentencias try para localizar una coincidencia. Este proceso continúa hasta que se encuentra la coincidencia catch adecuada o hasta que se acaban las sentencias try. Si no se encuentra ninguna, el intérprete Java gestionará la excepción.

Las sentencias try anidadas pueden darse también de una forma menos obvia cuando se implican las llamadas a métodos. Por ejemplo, se puede incluir una llamada a un método dentro de un bloque try, y si dentro de este método hay otra sentencia try, esta se encuentra nidada dentro del bloque try externo que llama al método.

En PC Resumen hablaremos de que son las Excepciones de Java y como controlarlas. Una excepción a Java es un objeto que describe una condición excepcional, es decir, un error que se ha dado en una parte del código. Cuando aparece una condición excepcional, se crea un objeto que representa esta excepción y se lanza el método que ha causado el error. Este método puede escoger entre gestionar él mismo la excepción o pasarla. De cualquiera de las maneras, en un punto determinado, se cazará la excepción y se procesará. Las excepciones pueden generarse por el intérprete de Java o de forma manual por el propio código. Normalmente las excepciones generadas por Java están relacionadas con errores fundamentales que violan las reglas del lenguaje o las restricciones del entorno de ejecución.

La forma general de un bloque de gestión de excepciones es la siguiente:

try {
// bloque de código que controla los errores
}
catch (ExcepcionTipo1 exOb) {
// gestor de excepciones para Excepción Tipo 1
}
....
catch (ExcepcionTipoN exOb) {
// gestor de excepciones para Excepción Tipo N
}
finally {
// código que se ejecutará siempre
}

Excepciones no capturadas

Antes de ver cómo gestionar las excepciones, será útil ver que pasa cuando no se gestionan. El siguiente código provoca intencionadamente un error debido a una división por cero.

class Exc0 {
    public static void main(String args[]) {
        int d = 0;
        int a = 42 / d;
    }
}

Cuando el intérprete Java detecta un intento de división por cero, construye un nuevo objeto de excepción y luego la lanza. Esto provoca que la ejecución de este programa se detenga, porque una vez la excepción se ha lanzado, debe capturarse por un gestor de excepciones y tratarse de forma inmediata. Como en este ejemplo, no se han proporcionado gestores de excepciones, la excepción se captura por el gestor por defecto que proporciona Java. Cualquier excepción no capturada por el programa, finalmente la procesará el gestor por defecto, que presentará un mensaje describiendo la excepción, imprimiendo el trazado de la pila del programa y finalizando este.

La excepción generada por el programa siguiente sería:

java.langArithmeticException : /by zero at Exc0.main(Exc0.java:4)

Obsérvese que en el trazado de la pila, se incluye el nombre de la clase, el nombre del método, el nombre del archivo y el número de línea. Fijémonos también que el tipo de excepción lanzada, llamada ArithmeticException, describe de forma más especifica el tipo de error que se ha producido. Como veremos más adelante, Java proporciona bastante tipo de excepciones que se ajustan a las diferentes clases de errores que se pueden generar.

El trazado de la pila siempre mostrará la secuencia de llamadas a métodos que preceden al error. Por ejemplo, vemos otra versión del programa anteriores que introduce el mismo error pero en un método diferente:

class Exc1 {
    static void subrutina() {
        int d = 0;
        int a = 10 / d;
    }
    
    public static void main(String args[]) {
        Exc1.subrutina();
    }
}

En este caso el trazado de la pila muestra la pila de llamadas completa:

java.lang.ArithmeticException: /by zero
at Exc1.subrutina (Exc1.java:7)
at Exc1.main (Exc1.java:4)

La pila de llamadas es muy útil para la depuración ya que muestra la secuencia exacta de pasos que condujo al error.

En PC Resumen hablaremos de las interfaces en Java. Utilizando la palabra clave interface se puede abstraerse completamente la interfaz de una clase de su implementación. Es decir, se puede especificar el que una clase tiene que hacer, pero no cómo hacerlo. Las interfaces tienen una sintaxis similar a las clases, pero le faltan las variables de instancia y los métodos se declaran sin cuerpo. Una vez definida, cualquier número de clases puede implementar una interfaz. También una clase, puede implementar cualquier número de interfaces.

Para implementar una interfaz, una clase debe crear el conjunto completo de métodos definido por la interfaz.

Las interfaces llevan la mayor parte de la funcionalidad que se requiere en muchas aplicaciones. Otros lenguajes lo hacen recurriendo a la herencia múltiple (lo que existe en la teoría de la programación orientada a objetos).

Definición de una interfaz

Una interfaz se define como una clase. El acceso es público o no se utiliza. Cuando no se incluye el especificador de acceso, la interfaz sólo está disponible para otros miembros del paquete.

Los métodos que se declaran no tienen cuerpo y terminan en un punto y como después de la lista de parámetros. Son esencialmente métodos abstractos. Cada clase que incluya una interfaz debe implementar todos los métodos.

Las variables pueden declararse dentro de las declaraciones de la interfaz y son, implícitamente final y static; esto significa que no pueden ser alteradas por la implementación de la clase y que deben inicializarse con un valor constante.

Implementación de una interfaz

Una vez definida una interfaz, una o más clases pueden implementarla. Para hacerlo se debe incluir la palabra implements en una definición de clase, y luego crear los métodos definidos por la interfaz. El acceso debe ser público o por defecto.

Si una clase implementa más de una interfaz, las interfaces se separan por comas. Los métodos que implementan una interfaz deben declararse públicos. Aparte de esto se pueden definir todos los métodos que se quiera.

Acceso a implementaciones a través de referencias

Se pueden declarar variables como referencias a objetos que usan una interfaz en lugar de un tipo de clase. Se puede hacer referencia a cualquier instancia de cualquier clase que implementa una interfaz declarada por medio de estas variables. Cuando se llama a un método por medio de una de estas referencias, se llamará a la versión correcta que se basa en la instancia actual de la interfaz que está siendo referenciada. Esta es una de las características clave de las interfaces. Debido a que la búsqueda dinámica de un método durante la ejecución supone mayor tiempo de proceso, conviene tener precaución de no utilizar innecesariamente interfaces en códigos que tienen un rendimiento crítico.

Una variable de referencia a una interfaz sólo tiene conocimiento de los métodos que figuran en la declaración de la interfaz y no en el que se ha definido en las clases que la implementan.

Si una clase incluye una interfaz, pero no implementa completamente los métodos definidos por esta interfaz, debe ser declarada abstracta.

El acceso a múltiples implementaciones de una interfaz a través de una variable de referencia de la interfaz es la forma más eficaz de que dispone Java para conseguir el polimorfismo en tiempo de ejecución.

Se pueden utilizar las interfaces para importar constantes compartidas por múltiples clases declarando simplemente una interfaz que contiene variables inicializadas con los valores deseados. Cuando se incluye esta interfaz en una clase, todos estos nombres de variables estarán dentro del ámbito como constantes.

Extender una interfaz

Hay que decir finalmente que una interfaz también se puede heredar utilizando la palabra clave extends. La sintaxis es la misma que para la herencia de clases. Cuando una clase implementa una interfaz que hereda de otra interfaz, debe proporcionar las implementaciones para todos los métodos definidos en la cadena de herencia de la interfaz.