El constructor FileInputStream(String filename) crea un flujo de entrada al archivo identificado por filename. Este constructor genera FileNotFoundException cuando el archivo no existe, hace referencia a un directorio u ocurre otro problema relacionado. El constructor FileOutputStream(String filename) crea un flujo de salida al archivo identificado por filename. Genera FileNotFoundException cuando el archivo existe pero hace referencia a un directorio, no existe y no se puede crear, u ocurre otro problema relacionado. FileInputStream proporciona un método int read() para leer un byte y devolverlo como un entero de 32 bits. Este método devuelve -1 al final del archivo. FileOutputStream proporciona un método void write(int b) para escribir el byte en los 8 bits inferiores de b. Cualquiera de los métodos genera IOException cuando algo sale mal. La mayor parte del ejemplo es un bucle while que lee() repetidamente el siguiente byte del flujo de entrada y escribe() ese byte en el flujo de salida, hasta que read() señala el final del archivo. La lógica de copia de archivos del bloque try es fácil de seguir porque no se combina con código de comprobación de excepciones (pruebas if y sentencias throw relacionadas ocultas en los constructores y métodos), código de manejo de excepciones (que se ejecuta en uno o más bloques catch asociados) y código de limpieza (para cerrar los archivos de origen y destino; este código se relega a un bloque finally asociado). Por el contrario, la falta de un marco de trabajo orientado a excepciones similar en C da como resultado un código más verboso, como lo ilustra el siguiente extracto de una aplicación cp de C más grande (en el archivo de código de este artículo) que copia un archivo de origen a un archivo de destino: if ((fpsrc = fopen(argv[1]»rb»)) == NULL) { fprintf(stderr, «no se puede abrir %s para leern», argv[1]); devolver; } si ((fpdst = fopen(argv[2]»wb»)) == NULL) { fprintf(stderr, «no se puede abrir %s para escribirn», argv[1]); fclose(fpsrc); return; } mientras ((c = fgetc(fpsrc)) != EOF) si (fputc(c, fpdst) == EOF) { fprintf(stderr, «no se puede escribir en %sn», argv[1]); break; } En este ejemplo, la lógica de copia de archivo es más difícil de seguir porque la lógica está entremezclada con la comprobación de excepciones, el manejo de excepciones y el código de limpieza: Las dos comprobaciones == NULL y una == EOF son el equivalente de las sentencias throw ocultas y las comprobaciones relacionadas. Las tres llamadas a la función fprintf() son el código de manejo de excepciones cuyo equivalente en Java se ejecutaría en uno o más bloques catch. La llamada a la función fclose(fpsrc); es el código de limpieza cuyo equivalente en Java se ejecutaría en un bloque finally. Seguir try con catch o finally Un bloque try debe ir seguido de un bloque catch o un bloque finally. De lo contrario, el compilador informará un error. Esta regla no se aplica a la sentencia try-with-resources, que se analiza en la segunda mitad de este artículo. Usar bloques catch para capturar excepciones La capacidad de manejo de excepciones de Java se basa en bloques catch. Esta sección presenta catch y varios bloques catch. El bloque catch Java proporciona el bloque catch para delimitar una secuencia de sentencias que manejan una excepción. Un bloque catch tiene la siguiente sintaxis: catch (throwableType throwableObject) { // una o más instrucciones que manejan una excepción } El bloque catch es similar a un constructor en que tiene una lista de parámetros. Sin embargo, esta lista consta de un solo parámetro, que es un tipo de objeto arrojable (Throwable o una de sus subclases) seguido de un identificador para un objeto de ese tipo. Cuando ocurre una excepción, se crea un objeto arrojable y se lanza a la JVM, que busca el bloque catch más cercano cuyo tipo de parámetro coincida directamente o sea el supertipo del objeto arrojable lanzado. Cuando encuentra este bloque, la JVM pasa el objeto arrojable al parámetro y ejecuta las instrucciones del bloque catch, que pueden interrogar al objeto arrojable pasado y manejar la excepción de otra manera. Considere el siguiente ejemplo: catch (FileNotFoundException fnfe) { System.err.println(fnfe.getMessage()); } Este ejemplo (que extiende el ejemplo del bloque try anterior) describe un bloque catch que captura y maneja objetos arrojables de tipo FileNotFoundException. Este bloque solo captura los objetos que coinciden con este tipo o un subtipo. Supongamos que el constructor FileInputStream(String filename) lanza FileNotFoundException. La JVM comprueba el bloque catch después de try para ver si su tipo de parámetro coincide con el tipo de objeto. Al detectar una coincidencia, la JVM pasa la referencia del objeto a fnfe y transfiere la ejecución al bloque. El bloque responde invocando getMessage() para recuperar el mensaje de la excepción, que luego genera. Lanzar excepciones desde bloques catch Un bloque catch podría no ser capaz de manejar completamente una excepción; tal vez necesite acceder a la información proporcionada por algún método antecesor en la pila de llamadas a métodos. Si puede manejar parcialmente la excepción, el bloque catch debería concluir volviendo a lanzar la excepción para que un manejador antecesor pueda terminar de manejarla. Otra posibilidad es registrar la excepción (para un análisis posterior) y luego volver a lanzarla. Esa técnica se demuestra aquí: catch (FileNotFoundException fnfe) { logger.log(fnfe); throw fnfe; } Especificación de varios bloques catch Puede especificar varios bloques catch después de un bloque try. Por ejemplo, considere este extracto más grande de la aplicación Copy mencionada anteriormente: FileInputStream fis = null; FileOutputStream fos = null; { fis = new FileInputStream(args[0]); fos = nuevo FileOutputStream(argumentos)[1]); int c; while ((c = fis.read()) != -1) fos.write(c); } catch (FileNotFoundException fnfe) { System.err.println(fnfe.getMessage()); } catch (IOException ioe) { System.err.println(«Error de E/S: » + ioe.getMessage()); } El primer bloque catch maneja las FileNotFoundException lanzadas desde cualquiera de los constructores. El segundo bloque catch maneja las IOException lanzadas desde los métodos read() y write(). Al especificar varios bloques catch, no especifique un bloque catch con un supertipo antes de un bloque catch con un subtipo. Por ejemplo, no coloque catch (IOException ioe) antes de catch (FileNotFoundException fnfe). Si lo hace, el compilador informará un error porque catch (IOException ioe) también manejaría FileNotFoundExceptions, y catch (FileNotFoundException fnfe) nunca tendría la oportunidad de ejecutarse. De manera similar, no especifique múltiples bloques catch con el mismo tipo de objeto de lanzamiento. Por ejemplo, no especifique dos bloques catch (IOException ioe) {}. De lo contrario, el compilador informará un error. Uso de bloques finally para limpiar excepciones Independientemente de si se maneja o no una excepción, es posible que deba realizar tareas de limpieza, como cerrar un archivo abierto. Java proporciona el bloque finally para este propósito. El bloque finally consta de la palabra clave finally seguida de una secuencia de instrucciones delimitadas por llaves para ejecutar. Puede aparecer después del bloque catch final o después del bloque try. Limpieza en un contexto try-catch-finally Cuando se deben limpiar los recursos y no se lanza una excepción desde un método, se coloca un bloque finally después del bloque catch final. Esto se demuestra con el siguiente extracto de Copy: FileInputStream fis = null; FileOutputStream fos = null; intentar { fis = nuevo FileInputStream(args[0]); fos = nuevo FileOutputStream(argumentos)[1]); int c; while ((c = fis.read()) != -1) fos.write(c); } catch (FileNotFoundException fnfe) { System.err.println(fnfe.getMessage()); } catch (IOException ioe) { System.err.println(«Error de E/S: » + ioe.getMessage()); } finally { if (fis != null) try { fis.close(); } catch (IOException ioe) { // ignorar la excepción } if (fos != null) try { fos.close(); } catch (IOException ioe) { // ignorar la excepción } } Si el bloque try se ejecuta sin una excepción, la ejecución pasa al bloque finally para cerrar los flujos de entrada/salida del archivo. Si se lanza una excepción, el bloque finally se ejecuta después del bloque catch apropiado. FileInputStream y FileOutputStream heredan un método void close() que lanza IOException cuando no se puede cerrar el flujo. Por esta razón, he envuelto cada uno de fis.close(); y fos.close(); en un bloque try. He dejado el bloque catch asociado vacío para ilustrar el error común de ignorar una excepción. Un bloque catch vacío que se invoca con el objeto throwable apropiado no tiene forma de informar la excepción. Puede perder mucho tiempo rastreando la causa de la excepción, solo para descubrir que podría haberla detectado antes si el bloque catch vacío hubiera informado la excepción, incluso si solo fuera en un registro. Limpieza en un contexto try-finally Cuando se deben limpiar los recursos y se lanza una excepción de un método, se coloca un bloque finally después del bloque try: no hay bloques catch. Considere el siguiente extracto de una segunda versión de la aplicación Copy: public static void main(String[] args) { if (args.length != 2) { System.err.println(«uso: java Copiar archivo de origen archivo de destino»); return; } try { copy(args[0]argumentos[1]); } catch (IOException ioe) { System.err.println(«Error de E/S: » + ioe.getMessage()); } } static void copy(String srcFile, String dstFile) lanza IOException { FileInputStream fis = null; FileOutputStream fos = null; try { fis = new FileInputStream(srcFile); fos = new FileOutputStream(dstFile); int c; while ((c = fis.read()) != -1) fos.write(c); } finally { if (fis != null) try { fis.close(); } catch (IOException ioe) { System.err.println(ioe.getMessage()); } if (fos != null) try { fos.close(); } catch (IOException ioe) { System.err.println(ioe.getMessage()); } } } La lógica de copia de archivos se ha movido a un método copy(). Este método está diseñado para informar una excepción al llamador, pero primero cierra cada archivo abierto. La cláusula throws de este método solo enumera IOException. No es necesario incluir FileNotFoundException porque FileNotFoundException subclasifica IOException. Una vez más, la cláusula finally presenta mucho código solo para cerrar dos archivos. En la segunda parte de esta serie, aprenderá sobre la declaración try-with-resources, que obvia la necesidad de cerrar explícitamente estos archivos. En conclusión Este artículo le presentó los conceptos básicos del marco orientado a excepciones tradicional de Java, pero hay mucho más que comprender. La segunda mitad de este tutorial presenta las características del lenguaje orientado a excepciones más avanzadas de Java y los tipos de biblioteca, incluido try-with-resources.