Descarga la aplicación para disfrutar aún más
Vista previa del material en texto
Archivos, fl ujos y serialización de objetos 17 Sólo puedo suponer que un documento “No archivar” se archiva en un archivo “No archivar”. —Senador Frank Church Audiencia del subcomité de inteligencia del Senado, 1975 La conciencia … no aparece a sí misma cortada en pequeños pedazos. … Un “río” o un “flujo” son las metáforas por las cuales se describe con más naturalidad. —William James O b j e t i v o s En este capítulo aprenderá a: ■ Crear, leer, escribir y actualizar archivos. ■ Obtener información acerca de los archivos y directorios. ■ Comprender la jerarquía de clases de flujos de entrada/salida en Java. ■ Conocer las diferencias entre los archivos de texto y los archivos binarios. ■ Utilizar las clases Scanner y Formatter para procesar archivos de texto. ■ Utilizar las clases FileInputStream y FileOutputStream para leer de, y escribir en, archivos. ■ Utilizar las clases ObjectInputStream y ObjectOutputStream para leer objetos de, y escribir objetos en, archivos. ■ Utilizar un cuadro de diálogo JFileChooser. 720 Capítulo 17 Archivos, fl ujos y serialización de objetos 17.1 Introducción 17.2 Archivos y fl ujos 17.3 La clase File 17.4 Archivos de texto de acceso secuencial 17.4.1 Creación de un archivo de texto de acceso secuencial 17.4.2 Cómo leer datos de un archivo de texto de acceso secuencial 17.4.3 Caso de estudio: un programa de solicitud de crédito 17.4.4 Actualización de archivos de acceso secuencial 17.5 Serialización de objetos 17.5.1 Creación de un archivo de acceso secuencial mediante el uso de la serialización de objetos 17.5.2 Lectura y deserialización de datos de un archivo de acceso secuencial 17.6 Clases adicionales de java.io 17.6.1 Interfaces y clases para entrada y salida basada en bytes 17.6.2 Interfaces y clases para entrada y salida basada en caracteres 17.7 Abrir archivos con JFileChooser 17.8 Conclusión Resumen | Ejercicios de autoevaluación | Respuestas a los ejercicios de autoevaluación | Ejercicios | Marcar la diferencia 17.1 Introducción1 El almacenamiento de datos en variables y arreglos es temporal; los datos se pierden cuando una variable local queda fuera de alcance, o cuando el programa termina. Las computadoras utilizan archivos para la retención a largo plazo de datos, incluso hasta después de que terminan los programas que crean esos datos. Usted utiliza archivos a diario, para tareas como escribir un documento o crear una hoja de cálcu- lo. Las computadoras almacenan archivos en dispositivos de almacenamiento secundario como discos duros, discos ópticos y cintas magnéticas. Nos referimos a los datos que se mantienen en archivos como datos persistentes, ya que existen más allá de la duración de la ejecución del programa. En este capítulo explicaremos cómo los programas en Java crean, actualizan y procesan archivos. Empezaremos con un análisis sobre la arquitectura de Java para manejar archivos mediante programa- ción. Luego explicaremos que los datos pueden almacenarse en archivos de texto y archivos binarios, y cubriremos las diferencias entre ellos. Demostraremos cómo obtener información sobre archivos y direc- torios mediante el uso de la clase File, y después dedicaremos varias secciones a los distintos mecanismos para escribir datos en, y leer datos de, archivos. Le mostraremos cómo crear y manipular archivos de texto de acceso secuencial. Al trabajar con archivos de texto, el lector puede empezar a manipular archivos con rapidez y facilidad. Sin embargo, como veremos más adelante, es difícil leer datos de los archivos de texto y devolverlos al formato de los objetos. Por fortuna, muchos lenguajes orientados a objetos (incluyen- do Java) ofrecen distintas formas de escribir objetos en (y leer objetos de) archivos (lo que se conoce como serialización y deserialización de objetos). Para demostrar esto, recreamos algunos de los programas de acceso secuencial que utilizaban archivos de texto, esta vez almacenando objetos en archivos binarios. 17.2 Archivos y flujos Java considera a cada archivo como un flujo de bytes secuencial (figura 17.1). Cada sistema operativo proporciona un mecanismo para determinar el fin de un archivo, como el marcador de fin de archivo o la cuenta de bytes totales en el archivo que se registra en una estructura de datos administrativa, mantenida por el sistema. Un programa de Java que procesa un flujo de bytes simplemente recibe una indicación del sistema operativo cuando el programa llega al fin del flujo; el programa no necesita sa- ber cómo representa la plataforma subyacente a los archivos o flujos. En algunos casos, la indicación de 1 Las técnicas que se muestran en este capítulo se basan en Java SE 6. Java SE 7 introduce nuevas API en el sistema de ar- chivos para interactuar con los archivos y directorios. En el sitio Web complementario del libro (al que puede acceder en www.pearsonhighered.com/deitel) publicamos una versión de este capítulo, en el que implementamos el uso de estas API de Java SE 7. 17.2 Archivos y fl ujos 721 fin de archivo ocurre como una excepción. En otros casos, la indicación es un valor de retorno de un método invocado en un objeto procesador de flujos. Fig. 17.1 � La manera en que Java ve a un archivo de n bytes. 0 1 2 3 4 5 6 7 8 9 ... ... n-1 marcador de fin de archivo Flujos basados en bytes y basados en caracteres Los flujos de archivos se pueden utilizar para la entrada y salida de datos, ya sea como bytes o carac- teres. Los flujos basados en bytes reciben y envían datos en su formato binario. Los flujos basados en caracteres reciben y envían datos como una secuencia de caracteres. Por ejemplo, si se almacenara el valor 5 usando un flujo basado en bytes, sería en el formato binario del valor numérico 5, o 101. Si se almacenara el valor 5 usando un flujo basado en caracteres, sería en el formato binario del ca- rácter 5, o 00000000 00110101 (ésta es la representación binaria para el valor numérico 53, el cual indica el carácter 5 en el conjunto de caracteres Unicode®). La diferencia entre las dos formas es que el valor numérico se puede utilizar como un entero en los cálculos, mientras que el carácter 5 es simplemente un carácter que puede utilizarse en una cadena de texto, como en “Sarah Miller tiene 15 años de edad”. Los archivos que se crean usando flujos basados en bytes se conocen como archivos binarios, mientras que los archivos que se crean usando flujos basados en caracteres se conocen como archivos de texto. Los archivos de texto se pueden leer con editores de texto, mien- tras que los archivos binarios se leen mediante programas que comprenden el contenido específico del archivo y su orden. Flujos estándar de entrada salida y error estándar Un programa de Java abre un archivo creando un objeto y asociándole un flujo de bytes o de carac- teres. El constructor del objeto interactúa con el sistema operativo para abrir el archivo. Java tam- bién puede asociar flujos con distintos dispositivos. De hecho, Java crea tres objetos flujo que se asocian con dispositivos cuando un programa de Java empieza a ejecutarse: System.in, System.out y System.err. Por lo general, System.in (el objeto flujo de entrada estándar) permite a un pro- grama recibir bytes desde el teclado; el objeto System.out (el objeto flujo estándar de salida) gene- ralmente permite a un programa mostrar datos en la pantalla; y el objeto System.err (el objeto flujo estándar de error) normalmente permite a un programa mostrar en la pantalla mensajes de error basados en caracteres. Cada uno de estos flujos puede redirigirse. Para System.in, esta capaci- dad permite al programa leer bytes desde un origen distinto. Para System.out y System.err, esta capacidad permite que la salida se envíe a una ubicación distinta, como un archivo en disco. La clase System proporciona los métodos setIn, setOut y setErr para redirigir los flujos estándar de entrada, salida y error, respectivamente. El paquete java.ioLos programas de Java realizan el procesamiento de archivos utilizando clases del paquete java.io. Este paquete incluye definiciones para las clases de flujo como FileInputStream (para la entrada basada en bytes desde un archivo), FileOutputStream (para la salida basada en bytes hacia un ar- chivo), FileReader (para la entrada basada en caracteres desde un archivo) y FileWriter (para la salida basada en caracteres hacia un archivo), que heredan de las clases InputStream, OutputStream, Reader y Writer, respectivamente. Por lo tanto, los métodos de estas clases de flujos pueden aplicarse a los flujos de archivos también. 722 Capítulo 17 Archivos, fl ujos y serialización de objetos Java contiene clases que permiten al programador realizar operaciones de entrada y salida con ob- jetos o variables de tipos de datos primitivos. Los datos se siguen almacenando como bytes o caracteres tras bambalinas, lo cual permite al programador leer o escribir datos en forma de valores int, String u otros tipos de datos, sin tener que preocuparse por los detalles acerca de convertir dichos valores al formato de bytes. Para realizar dichas operaciones de entrada y salida, pueden usarse objetos de las clases ObjectInputStream y ObjectOutputStream junto con las clases de flujos de archivos basadas en bytes FileInputStream y FileOutputStream (en breve hablaremos con más detalle sobre estas clases). Es posible consultar la jerarquía completa de tipos del paquete java.io en la documentación en línea, en la página: download.oracle.com/javase/6/docs/api/java/io/package-tree.html Como puede ver en la jerarquía, Java ofrece muchas clases para realizar operaciones de entrada/salida. En este capítulo usaremos varias de estas clases para implementar programas de procesamiento de ar- chivos, que crean y manipulan archivos de acceso secuencial. En el capítulo 27 utilizaremos las clases de flujos en forma extensa, para implementar aplicaciones de red. Además de las clases de java.io, las operaciones de entrada y salida basadas en caracteres se pue- den llevar a cabo con las clases Scanner y Formatter. La clase Scanner se utiliza con mucha frecuen- cia para recibir datos del teclado; también puede leer datos desde un archivo. La clase Formatter permite mostrar datos con formato a cualquier flujo basado en texto, en forma similar al método System.out.printf. En el apéndice G, se presentan los detalles acerca de la salida con formato me- diante printf. Todas estas características se pueden utilizar también para dar formato a los archivos de texto. 17.3 La clase File En esta sección presentamos la clase File, que es especialmente útil para recuperar información acerca de un archivo o directorio de un disco. Los objetos de la clase File no abren archivos ni proporcionan herramientas para procesarlos. No obstante, los objetos File se utilizan frecuentemente con objetos de otras clases de java.io para especificar los archivos o directorios que van a manipularse. Creación de objetos File La clase File proporciona cuatro constructores. El constructor con un argumento String especifica el nombre de un archivo o directorio que se asociará con el objeto File. El nombre puede conte- ner información sobre la ruta, así como el nombre de un archivo o directorio. La ruta de un archivo o directorio especifica su ubicación en el disco. La ruta incluye algunos o todos los directorios que conducen a ese archivo o directorio. Una ruta absoluta contiene todos los directorios, empezando con el directorio raíz, que conducen a un archivo o directorio específico. Cada archivo o directorio en un disco duro específico tiene el mismo directorio raíz en su ruta. Por lo general, una ruta relativa empieza desde el directorio en el que la aplicación empezó a ejecutarse, y es por lo tanto una ruta “relativa” al directorio actual. El constructor con dos argumentos String especifica una ruta abso- luta o relativa como el primer argumento, y el archivo o directorio a asociar con el objeto File como el segundo argumento. El constructor con argumentos File y String usa un objeto File existente, que especifica el directorio padre del archivo o directorio especificado por el argumento String. El cuarto constructor usa un objeto URI para localizar el archivo. Un Identificador uniforme de recursos (URI) es una forma más general de un Localizador uniforme de recursos (URL), el cual se utiliza para localizar sitios Web. Por ejemplo, http://www.deitel.com/ es el URL para el sitio Web de Deitel & Associates. Los URI para localizar archivos varían entre los distintos sistemas operativos. En plataformas Windows, el URI: file://C:/datos.txt 17.3 La clase File 723 identifica al archivo datos.txt, almacenado en el directorio raíz de la unidad C:. En plataformas UNIX/Linux, el URI file:/home/estudiante/datos.txt identifica el archivo datos.txt almacenado en el directorio home del usuario estudiante. La figura 17.2 muestra una lista de los métodos más comunes de File. En download.oracle.com/ javase/6/docs/api/java/io/File.html encontrará la lista completa. Fig. 17.2 � Métodos de File. Método Descripción boolean canRead() Devuelve true si la aplicación actual puede leer un archivo; false en caso contrario. boolean canWrite() Devuelve true si la aplicación actual puede escribir en un archivo; false en caso contrario. boolean exists() Devuelve true si el archivo o directorio representado por el objeto File existe; false en caso contrario. boolean isFile() Devuelve true si el nombre especifi cado como argumento para el constructor de File es un archivo; false en caso contrario. boolean isDirectory() Devuelve true si el nombre especifi cado como argumento para el constructor de File es un directorio; false en caso contrario. boolean isAbsolute() Devuelve true si los argumentos especifi cados para el constructor de File indican una ruta absoluta a un archivo o directorio; false en caso contrario. String getAbsolutePath() Devuelve un objeto String con la ruta absoluta del archivo o directorio. String getName() Devuelve un objeto String con el nombre del archivo o directorio. String getPath() Devuelve un objeto String con la ruta del archivo o directorio. String getParent() Devuelve un objeto String con el directorio padre del archivo o directorio (es decir, el directorio en el que puede encontrarse ese archivo o directorio). long length() Devuelve la longitud del archivo, en bytes. Si el objeto File representa a un directorio, se devuelve un valor no especifi cado. long lastModified() Devuelve una representación dependiente de la plataforma de la hora en la que se hizo la última modifi cación en el archivo o directorio. El valor devuelto es útil sólo para compararlo con otros valores devueltos por este método. String[] list() Devuelve un arreglo de objetos String, los cuales representan el contenido de un directorio. Devuelve null si el objeto File no representa a un directorio. Demostración de la clase File La figura 17.3 pide al usuario que introduzca el nombre de un archivo o directorio, y después usa la clase File para imprimir información en pantalla acerca del nombre de archivo o directorio intro- ducido. El programa empieza pidiendo al usuario un archivo o directorio (línea 12). En la línea 13 se introduce el nombre del archivo o directorio y se pasa al método analizarRuta (líneas 17 a 50). El método crea un nuevo objeto File (línea 20) y asigna su referencia a nombre. En la línea 22 se 724 Capítulo 17 Archivos, fl ujos y serialización de objetos Fig. 17.3 � Uso de la clase File para obtener información sobre archivos y directorios (parte 1 de 2). 1 // Fig. 17.3: DemostracionFile.java 2 // Clase File utilizada para obtener información sobre archivos y directorios. 3 import java.io.File; 4 import java.util.Scanner; 5 6 public class DemostracionFile 7 { 8 public static void main( String[] args ) 9 { 10 Scanner entrada = new Scanner( System.in ); 11 12 System.out.print(“Escriba aqui el nombre del archivo o directorio: ” ); 13 analizarRuta( entrada.nextLine() ); 14 } // fin de main 15 16 // muestra información acerca del archivo especificado por el usuario 17 public static void analizarRuta( String ruta ) 18 { 19 // crea un objeto File con base en la entrada del usuario 20 File nombre = new File( ruta ); 21 22 if ( nombre.exists() ) // si existe el nombre, muestra información sobre él 23 { 24 // muestra información del archivo (o directorio) 25 System.out.printf( 26 “%s%s\n%s\n%s\n%s\n%s%s\n%s%s\n%s%s\n%s%s\n%s%s”, 27 nombre.getName(), “ existe”, 28 ( nombre.isFile() ? “es un archivo” : “no es un archivo” ), 29 ( nombre.isDirectory() ? “es un directorio” : 30 “no es un directorio” ), 31 ( nombre.isAbsolute() ? “es ruta absoluta” : 32 “no es ruta absoluta” ), “Ultima modificacion: ”, 33 nombre.lastModified(), “Tamanio: ”, nombre.length(), 34 “Ruta: ”, nombre.getPath(), “Ruta absoluta: ”, 35 nombre.getAbsolutePath(), “Padre: ”, nombre.getParent() ); 36 37 if ( nombre.isDirectory() ) // muestra el listado del directorio 38 { 39 String[] directorio = nombre.list(); 40 System.out.println( “\n\nContenido del directorio:\n” ); 41 42 for ( String nombreDirectorio : directorio ) 43 System.out.printf( “%s\n”, nombreDirectorio ); 44 } // fin de if 45 } // fin de if exterior 46 else // no es archivo o directorio, muestra mensaje de error 47 { 48 System.out.printf( “%s %s”, ruta, “no existe.” ); 49 } // fin de else 50 } // fin del método analizarRuta 51 } // fin de la clase DemostracionFile 17.3 La clase File 725 invoca el método exists de File para determinar si el nombre introducido por el usuario existe (ya sea como archivo o directorio) en el disco. Si el nombre no existe, el control procede a las líneas 46 a 49 y muestra un mensaje en la pantalla, que contiene el nombre que escribió el usuario, seguido de “no existe”. En caso contrario, se ejecuta el cuerpo de la instrucción if (líneas 22 a 45). El programa imprime el nombre del archivo o directorio (línea 27), seguido de los resultados de probar el objeto File con isFile (línea 28), isDirectory (línea 29) e isAbsolute (línea 31). A continuación, el pro- grama muestra los valores devueltos por lastModified (línea 33), length (línea 33), getPath (línea 34), getAbsolutePath (línea 35) y getParent (línea 35). Si el objeto File representa un directorio (línea 37), el programa obtiene una lista del contenido del directorio como un arreglo de objetos String, usando el método list de File (línea 39), y muestra la lista en la pantalla. El primer resultado de este programa demuestra un objeto File asociado con el directorio jfc del JDK. El segundo resultado demuestra un objeto File asociado con el archivo README.txt del Fig. 17.3 � Uso de la clase File para obtener información sobre archivos y directorios (parte 2 de 2). Escriba aqui el nombre del archivo o directorio: E:\Archivos de programa\Java\ jdk1.6.0_11\demo\jfc jfc existe no es un archivo es un directorio es ruta absoluta Ultima modificacion: 1228404395024 Tamanio: 4096 Ruta: E:\Archivos de programa\Java\jdk1.6.0_11\demo\jfc Ruta absoluta: E:\Archivos de programa\Java\jdk1.6.0_11\demo\jfc Padre: E:\Archivos de programa\Java\jdk1.6.0_11\demo Contenido del directorio: CodePointIM FileChooserDemo Font2DTest Java2D Laffy Metalworks Notepad SampleTree Stylepad SwingApplet SwingSet2 SwingSet3 Escriba aqui el nombre del archivo o directorio: E:\Archivos de programa\Java\ jdk1.6.0_11\demo\jfc\Java2D\README.txt readme.txt existe es un archivo no es un directorio es ruta absoluta Ultima modificacion: 1228404384270 Tamanio: 7518 Ruta: E:\Archivos de programa\Java\jdk1.6.0_11\demo\jfc\Java2D\README.txt Ruta absoluta: E:\Archivos de programa\Java\jdk1.6.0_11\demo\jfc\Java2D\README.txt Padre: E:\Archivos de programa\Java\jdk1.6.0_11\demo\jfc\Java2D 726 Capítulo 17 Archivos, fl ujos y serialización de objetos ejemplo de Java 2D que viene con el JDK. En ambos casos, especificamos una ruta absoluta en nues- tra computadora personal. Un carácter separador se utiliza para separar directorios y archivos en la ruta. En un equipo Windows, el carácter separador es la barra diagonal inversa (\). En un sistema UNIX, el carácter separa- dor es la barra diagonal (/). Java procesa ambos caracteres en forma idéntica en el nombre de una ruta. Por ejemplo, si deseamos utilizar la ruta c:\Archivos de programa\Java\jdk1.6.0_11\demo/jfc que emplea uno de cada uno de los caracteres separadores antes mencionados, Java de todas formas pro- cesa la ruta en forma apropiada. Al construir objetos String que representen la información de una ruta, use File.separator para obtener el carácter separador apropiado del equipo local, en vez de utilizar / o \ de manera explícita. Esta constante devuelve un objeto String que consiste de un carácter: el separador apropiado para el sistema. Error común de programación 17.1 Usar \ como separador de directorios en vez de \\ en una literal de cadena es un error lógico. Una sola \ indica que la \ y el siguiente carácter representan una secuencia de escape. Para insertar una \ en una literal de cadena, debe usar \\. 17.4 Archivos de texto de acceso secuencial A continuación crearemos y manipularemos archivos de acceso secuencial, en donde se guardan los re- gistros en orden, en base al campo clave de registro. Empezaremos con los archivos de texto, que permi- ten al lector crear y editar rápidamente archivos que puedan ser leídos por los humanos. Hablaremos sobre crear, escribir datos en, leer datos de y actualizar los archivos de texto de acceso secuencial. También incluiremos un programa de consulta de crédito para obtener datos específicos de un archivo. 17.4.1 Creación de un archivo de texto de acceso secuencial Java no impone una estructura en un archivo; las nociones tales como los registros no existen como parte del lenguaje Java. Por lo tanto, el programador debe estructurar los archivos de manera que cumplan con los requerimientos de sus aplicaciones. En el siguiente ejemplo veremos cómo imponer una estructura de registros con claves en un archivo. El programa de las figuras 17.4, 17.5 y 17.8 crea un archivo simple de acceso secuencial, que podría utilizarse en un sistema de cuentas por cobrar para ayudar a administrar el dinero que deben a una com- pañía los clientes a crédito. Por cada cliente, el programa obtiene un número de cuenta, el nombre del cliente y su saldo (es decir, el monto que el cliente aún debe a la compañía por los bienes y servicios reci- bidos). Los datos obtenidos para cada cliente constituyen un “registro” para ese cliente. El número de cuenta se utiliza como la clave de registro en esta aplicación; el archivo se creará y mantendrá en orden basado en el número de cuenta. El programa supone que el usuario introduce los registros en orden de número de cuenta. En un sistema completo de cuentas por cobrar (basado en archivos de acceso secuen- cial), se proporcionaría una herramienta para ordenar datos, de manera que el usuario pudiera introducir los registros en cualquier orden. Después, los registros se ordenarían y se escribirían en el archivo. La clase RegistroCuenta La clase RegistroCuenta (figura 17.4) encapsula la información de registro del cliente utilizada por los ejemplos en este capítulo. La clase RegistroCuenta se declara en el paquete com.deitel.cap17 (línea 3), de forma que se pueda importar en varios ejemplos de este capítulo para reutilizarla (en la sección 8.14 encontrará información sobre cómo compilar y usar sus propios paquetes). La clase RegistroCuenta contiene las variables de instancia private llamadas cuenta, primerNombre, apellidoPaterno y saldo (líneas 7 a 10),además de los métodos establecer y obtener para acceder a 17.4 Archivos de texto de acceso secuencial 727 estos campos. Aunque los métodos establecer no validan los datos en este ejemplo, deberían hacerlo en un sistema de “nivel industrial”. Fig. 17.4 � La clase RegistroCuenta mantiene la información para una cuenta (parte 1 de 2). 1 // Fig. 17.4: RegistroCuenta.java 2 // La clase RegistroCuenta mantiene la información de una cuenta. 3 package com.deitel.cap17; // se empaqueta para reutilizarla 4 5 public class RegistroCuenta 6 { 7 private int cuenta; 8 private String primerNombre; 9 private String apellidoPaterno; 10 private double saldo; 11 12 // el constructor sin argumentos llama a otro constructor con valores predeterminados 13 public RegistroCuenta() 14 { 15 this( 0, “”, “”, 0.0 ); // llama al constructor con cuatro argumentos 16 } // fin del constructor de RegistroCuenta sin argumentos 17 18 // inicializa un registro 19 public RegistroCuenta( int cta, String nombre, String apellido, double sal ) 20 { 21 establecerCuenta( cta ); 22 establecerPrimerNombre( nombre ); 23 establecerApellidoPaterno( apellido ); 24 establecerSaldo( sal ); 25 } // fin del constructor de RegistroCuenta con cuatro argumentos 26 27 // establece el número de cuenta 28 public void establecerCuenta( int cta ) 29 { 30 cuenta = cta; 31 } // fin del método establecerCuenta 32 33 // obtiene el número de cuenta 34 public int obtenerCuenta() 35 { 36 return cuenta; 37 } // fin del método obtenerCuenta 38 39 // establece el primer nombre 40 public void establecerPrimerNombre( String nombre ) 41 { 42 primerNombre = nombre; 43 } // fin del método establecerPrimerNombre 44 45 // obtiene el primer nombre 46 public String obtenerPrimerNombre() 47 { 48 return primerNombre; 49 } // fin del método obtenerPrimerNombre 728 Capítulo 17 Archivos, fl ujos y serialización de objetos Para compilar la clase RegistroCuenta, abra una ventana de símbolo del sistema, cambie al direc- torio fig17_05 de este capítulo (que contiene el archivo RegistroCuenta.java) y escriba lo siguiente: javac -d .. RegistroCuenta.java Esto coloca a RegistroCuenta.class en la estructura de directorios de su paquete, y coloca el paquete en la carpeta cap17 que contiene todos los ejemplos para este capítulo. Cuando compile la clase RegistroCuen- ta (o cualquier otra clase que se reutilice en este capítulo), debe colocarla en un directorio común. Cuando compile o ejecute clases que utilicen a RegistroCuenta (por ejemplo, CrearArchivoTexto en la figura 17.5), debe especificar el argumento de línea de comandos –classpath para javac y java, como en javac -classpath .;c:\ejemplos\cap17 CrearArchivoTexto.java java -classpath .;c:\ejemplos\cap17 CrearArchivoTexto El directorio actual (que se especifica con .) se incluye en la ruta de clases para asegurar que el compila- dor pueda localizar otras clases en el mismo directorio que el de la clase que se está compilando. El se- parador de ruta que se utiliza en los comandos anteriores debe ser el apropiado para su plataforma: un punto y coma (;) en Windows y dos puntos (:) en UNIX/Linux/Mac OS X. Los anteriores comandos asumen que el paquete que contiene a RegistroCuenta se encuentra en el directorio C:\ejemplos\cap17 en un equipo Windows. La clase CrearArchivoTexto Ahora examinemos la clase CrearArchivoTexto (figura 17.5). La línea 14 declara la variable Formatter llamada salida. Como vimos en la sección 17.2, un objeto Formatter muestra en pantalla objetos Fig. 17.4 � La clase RegistroCuenta mantiene la información para una cuenta (parte 2 de 2). 50 51 // establece el apellido paterno 52 public void establecerApellidoPaterno( String apellido ) 53 { 54 apellidoPaterno = apellido; 55 } // fin del método establecerApellidoPaterno 56 57 // obtiene el apellido paterno 58 public String obtenerApellidoPaterno() 59 { 60 return apellidoPaterno; 61 } // fin del método obtenerApellidoPaterno 62 63 // establece el saldo 64 public void establecerSaldo( double sal ) 65 { 66 saldo = sal; 67 } // fin del método establecerSaldo 68 69 // obtiene el saldo 70 public double obtenerSaldo() 71 { 72 return saldo; 73 } // fin del método obtenerSaldo 74 } // fin de la clase RegistroCuenta 17.4 Archivos de texto de acceso secuencial 729 String con formato, usando las mismas herramientas de formato que el método System.out.printf. Un objeto Formatter puede enviar datos a varias ubicaciones, como la pantalla o a un archivo, como lo hacemos aquí. El objeto Formatter se instancia en la línea 21, en el método abrirArchivo (líneas 17 a 34). El constructor que se utiliza en la línea 21 recibe un argumento: un objeto String que contiene el nombre del archivo, incluyendo su ruta. Si no se especifica una ruta, como se da aquí el caso, la JVM asume que los archivos están en el directorio desde el cual se ejecutó el programa. Para los archivos de texto, utilizamos la extensión .txt. Si el archivo no existe, se creará. Si se abre un archivo existente, su contenido se trunca; todos los datos en el archivo se descartan. En este punto, el archivo se abre para escritura y el objeto Formatter resultante se puede utilizar para escribir datos en el archivo. Fig. 17.5 � Escribir datos en un archivo de texto secuencial mediante la clase Formatter (parte 1 de 3). 1 // Fig. 17.5: CrearArchivoTexto.java 2 // Escribir datos en un archivo de texto secuencial mediante la clase Formatter. 3 import java.io.FileNotFoundException; 4 import java.lang.SecurityException; 5 import java.util.Formatter; 6 import java.util.FormatterClosedException; 7 import java.util.NoSuchElementException; 8 import java.util.Scanner; 9 10 import com.deitel.cap17.RegistroCuenta; 11 12 public class CrearArchivoTexto 13 { 14 private Formatter salida; // objeto usado para enviar texto al archivo 15 16 // permite al usuario abrir el archivo 17 public void abrirArchivo() 18 { 19 try 20 { 21 salida = new Formatter( “clientes.txt” ); // abre el archivo 22 } // fin de try 23 catch ( SecurityException securityException ) 24 { 25 System.err.println( 26 “No tiene acceso de escritura a este archivo.” ); 27 System.exit( 1 ); // termina el programa 28 } // fin de catch 29 catch ( FileNotFoundException fileNotFoundException ) 30 { 31 System.err.println( “Error al abrir o crear el archivo.” ); 32 System.exit( 1 ); // termina el programa 33 } // fin de catch 34 } // fin del método abrirArchivo 35 36 // agrega registros al archivo 37 public void agregarRegistros() 38 { 39 // objeto que se va a escribir en el archivo 40 RegistroCuenta registro = new RegistroCuenta(); 730 Capítulo 17 Archivos, fl ujos y serialización de objetos 41 42 Scanner entrada = new Scanner( System.in ); 43 44 System.out.printf( “%s\n%s\n%s\n%s\n\n”, 45 “Para terminar la entrada, escriba el indicador de fin de archivo ”, 46 “cuando se le pida que escriba los datos de entrada.”, 47 “En UNIX/Linux/Mac OS X escriba <ctrl> d y oprima Intro”, 48 “En Windows escriba <ctrl> z y oprima Intro” ); 49 50 System.out.printf( “%s\n%s”, 51 “Escriba el numero de cuenta (> 0), primer nombre, apellido paterno y saldo.”, 52 “? ” ); 53 54 while ( entrada.hasNext() ) // itera hasta encontrar el indicador de fin de archivo 55 { 56 try // envía valores al archivo 57 { 58 // obtiene los datos que se van a enviar 59 registro.establecerCuenta( entrada.nextInt() ); // lee el número de cuenta 60 registro.establecerPrimerNombre( entrada.next() ); // lee el primer nombre 61 registro.establecerApellidoPaterno(entrada.next() ); // lee el apellido paterno 62 registro.establecerSaldo( entrada.nextDouble() ); // lee el saldo 63 64 if ( registro.obtenerCuenta() > 0 ) 65 { 66 // escribe el nuevo registro 67 salida.format( “%d %s %s %.2f\n”, registro.obtenerCuenta(), 68 registro.obtenerPrimerNombre(), registro.obtenerApellidoPaterno(), 69 registro.obtenerSaldo() ); 70 } // fin de if 71 else 72 { 73 System.out.println( 74 “El numero de cuenta debe ser mayor que 0.” ); 75 } // fin de else 76 } // fin de try 77 catch ( FormatterClosedException formatterClosedException ) 78 { 79 System.err.println( “Error al escribir en el archivo.” ); 80 return; 81 } // fin de catch 82 catch ( NoSuchElementException elementException ) 83 { 84 System.err.println( “Entrada invalida. Intente de nuevo.” ); 85 entrada.nextLine(); // descarta la entrada para que el usuario intente de nuevo 86 } // fin de catch 87 88 System.out.printf( “%s %s\n%s”, “Escriba el numero de cuenta (> 0),”, 89 “primer nombre, apellido paterno y saldo.”, “? ” ); 90 } // fin de while 91 } // fin del método agregarRegistros 92 Fig. 17.5 � Escribir datos en un archivo de texto secuencial mediante la clase Formatter (parte 2 de 3). 17.4 Archivos de texto de acceso secuencial 731 En las líneas 23 a 28 se maneja la excepción tipo SecurityException, que ocurre si el usuario no tiene permiso para escribir datos en el archivo. En las líneas 29 a 33 se maneja la excepción tipo FileNotFoundException, que ocurre si el archivo no existe y no se puede crear uno nuevo. Esta excep- ción también puede ocurrir si hay un error al abrir el archivo. En ambos manejadores de excepciones podemos llamar al método static System.exit, y pasarle el valor 1. Este método termina la aplicación. Un argumento de 0 para el método exit indica la terminación exitosa del programa. Un valor distinto de cero, como el 1 en este ejemplo, por lo general indica que ocurrió un error. Este valor se pasa a la ven- tana de comandos en la que se ejecutó el programa. El argumento es útil si el programa se ejecuta desde un archivo de procesamiento por lotes en los sistemas Windows, o una secuencia de comandos de shell en sistemas UNIX/Linux/Mac OS X. Los archivos de procesamiento por lotes y las secuen- cias de comandos de shell ofrecen una manera conveniente de ejecutar varios programas en secuencia. Cuando termina el primer programa, el siguiente programa empieza su ejecución. Es posible utilizar el argumento para el método exit en un archivo de procesamiento por lotes o secuencia de comandos de shell, para determinar si deben ejecutarse otros programas. Para obtener más información acerca de los archivos de procesamiento por lotes o las secuencias de comandos de shell, consulte la documenta- ción de su sistema operativo. El método agregarRegistros (líneas 37 a 91) pide al usuario que introduzca los diversos cam- pos para cada registro, o la secuencia de teclas de fin de archivo cuando termine de introducir los datos. La figura 17.6 enlista las combinaciones de teclas para introducir el fin de archivo en varios sis- temas computacionales. 93 // cierra el file 94 public void cerrarArchivo() 95 { 96 if ( salida != null ) 97 salida.close(); 98 } // fin del método cerrarArchivo 99 } // fin de la clase CrearArchivoTexto Fig. 17.5 � Escribir datos en un archivo de texto secuencial mediante la clase Formatter (parte 3 de 3). Fig. 17.6 � Combinaciones de teclas de fin de archivo. Sistema operativo Combinación de teclas UNIX/Linux/Mac OS X <Enter> <Ctrl> d Windows <Ctrl> z En la línea 40 se crea un objeto RegistroCuenta, el cual se utilizará para almacenar los valores del registro actual introducido por el usuario. En la línea 42 se crea un objeto Scanner para leer la entra- da del usuario mediante el teclado. En las líneas 44 a 48 y 50 a 52 se pide al usuario que introduzca los datos. En la línea 54 se utiliza el método hasNext de Scanner para determinar si se ha introducido la combinación de teclas de fin de archivo. El ciclo se ejecuta hasta que hasNext encuentra los indica- dores de fin de archivo. En las líneas 59 a 62 se leen datos del usuario y se almacena la información del registro en el ob- jeto RegistroCuenta. Cada instrucción lanza una excepción tipo NoSuchElementException (que se maneja en las líneas 82 a 86) si los datos se encuentran en el formato incorrecto (por ejemplo, un ob- jeto String cuando se espera un valor int), o si no hay más datos que introducir. Si el número de cuenta es mayor que 0 (línea 64), la información del registro se escribe en clientes.txt (líneas 67 a 69) me- diante el método format, que puede efectuar un formato idéntico al del método System.out.printf, que se utilizó en muchos de los ejemplos de capítulos anteriores. El método format envía un objeto 732 Capítulo 17 Archivos, fl ujos y serialización de objetos String con formato al destino de salida del objeto Formatter, en este caso el archivo clientes.txt. La cadena de formato “%d &s &s &.2f\n” indica que el registro actual se almacenará como un entero (el número de cuenta) seguido de un objeto String (el primer nombre), otra String (el apellido paterno) y un valor de punto flotante (el saldo). Cada pieza de información se separa de la siguiente mediante un espacio, y el valor tipo double (el saldo) se imprime en pantalla con dos dígitos a la derecha del punto decimal (como lo indica el .2 en %.2f). Los datos en el archivo de texto se pueden ver con un editor, o posteriormente mediante un programa diseñado para leer el archivo (sección 17.4.2). Cuando se ejecutan las líneas 67 a 69, si se cierra el objeto Formatter se lanza una excepción tipo FormatterClosedException. Esta excepción se maneja en las líneas 77 a 81. [Nota: también puede enviar datos a un archivo de texto mediante la clase java.io.PrintWriter, la cual también cuenta con los mé- todos format y printf para imprimir datos con formato]. En las líneas 94 a 98 se declara el método cerrarArchivo, el cual cierra el objeto Formatter y el archivo de salida subyacente. En la línea 97 se cierra el objeto, mediante una llamada simple al método close. Si el método close no se llama en forma explícita, el sistema operativo comúnmente cierra el archivo cuando el programa termina de ejecutarse; éste es un ejemplo de las “tareas de mantenimiento” del sistema operativo. Sin embargo, siempre debemos cerrar un archivo en forma explícita cuando ya no lo necesitemos. Caracteres separadores de línea específicos de la plataforma En las líneas 67 a 69 se imprime una línea de texto seguida de una nueva línea (\n). Si usa un editor de texto para abrir el archivo clientes.txt que se produce, tal vez no todos los registros se muestre en una línea separada. Por ejemplo, en el Bloc de notas (Microsoft Windows) los usuarios verán una línea continua de texto. Esto ocurre debido a que las distintas plataformas usan distintos caracteres separadores de líneas. En UNIX/Linux/Mac OS X, el separador de líneas es una nueva línea (\n). En Windows, es una combinación de retorno de carro y avance de línea, lo cual se representa como \r\n. Puede usar el especificador de formato %n en una cadena de control de formato para imprimir un se- parador de línea específico de la plataforma, con lo cual asegura que sea posible abrir y ver el archivo de texto correctamente en un editor de texto para la plataforma en la que se creó el archivo. El método System.out.println imprime un separador de líneas específico de la plataforma después de su argu- mento. Además, sin importar el separador de línea que se utilice en un archivo de texto, un programa de Java puede aún reconocer las líneas de textoy leerlas. La clase PruebaCrearArchivoTexto La figura 17.7 ejecuta el programa. En la línea 8 se crea un objeto CrearArchivoTexto, el cual se utiliza posteriormente para abrir, agregar registros y cerrar el archivo (líneas 10 a 12). Los datos de ejemplo para esta aplicación se muestran en la figura 17.8. En la ejecución de ejemplo para este pro- grama, el usuario introduce información para cinco cuentas, y después introduce el fin de archivo para indicar que ha terminado de introducir datos. La ejecución de ejemplo no muestra cómo apare- cen realmente los registros de datos en el archivo. En la siguiente sección, para verificar que el archivo se haya creado sin problemas, presentamos un programa que lee el archivo e imprime su contenido. Como es un archivo de texto, también puede verificar la información con sólo abrir el archivo en un editor de texto. Fig. 17.7 � Prueba de la clase CrearArchivoTexto (parte 1 de 2). 1 // Fig. 17.7: PruebaCrearArchivoTexto.java 2 // Prueba de la clase CrearArchivoTexto. 3 4 public class PruebaCrearArchivoTexto 5 { 17.4 Archivos de texto de acceso secuencial 733 17.4.2 Cómo leer datos de un archivo de texto de acceso secuencial Los datos se almacenan en archivos, para poder procesarlos según sea necesario. En la sección 17.4.1 demostramos cómo crear un archivo para acceso secuencial. Esta sección muestra cómo leer los datos en forma secuencial desde un archivo de texto. Demostraremos cómo puede utilizarse la clase Scanner para recibir datos de un archivo, en vez del teclado. La aplicación de las figuras 17.9 y 17.10 lee registros del archivo “clientes.txt” creado por la aplicación de la sección 17.4.1 y muestra el contenido de los registros. En la línea 13 de la figura 17.9 se declara un objeto Scanner, que se utilizará para obtener los datos de entrada del archivo. Fig. 17.7 � Prueba de la clase CrearArchivoTexto (parte 2 de 2). 6 public static void main( String[] args ) 7 { 8 CrearArchivoTexto aplicacion = new CrearArchivoTexto(); 9 10 aplicacion.abrirArchivo(); 11 aplicacion.agregarRegistros(); 12 aplicacion.cerrarArchivo(); 13 } // fin de main 14 } // fin de la clase PruebaCrearArchivoTexto Para terminar la entrada, escriba el indicador de fin de archivo cuando se le pida que escriba los datos de entrada. En UNIX/Linux/Mac OS X escriba <ctrl> d y oprima Intro En Windows escriba <ctrl> z y oprima Intro Escriba el numero de cuenta (> 0), primer nombre, apellido paterno y saldo. ? 100 Bob Jones 24.98 Escriba el numero de cuenta (>0), primer nombre, apellido paterno y saldo. ? 200 Steve Doe -345.67 Escriba el numero de cuenta (>0), primer nombre, apellido paterno y saldo. ? 300 Pam White 0.00 Escriba el numero de cuenta (>0), primer nombre, apellido paterno y saldo. ? 400 Sam Stone -42.16 Escriba el numero de cuenta (>0), primer nombre, apellido paterno y saldo. ? 500 Sue Rich 224.62 Escriba el numero de cuenta (>0), primer nombre, apellido paterno y saldo. ? ^Z Fig. 17.8 � Datos de ejemplo para el programa de las figuras 17.5 a 17.7. Datos de ejemplo 100 Bob Jones 24.98 200 Steve Doe -345.67 300 Pam White 0.00 400 Sam Stone -42.16 500 Sue Rich 224.62 734 Capítulo 17 Archivos, fl ujos y serialización de objetos Fig. 17.9 � Lectura de un archivo secuencial mediante un objeto Scanner (parte 1 de 2). 1 // Fig. 17.9: LeerArchivoTexto.java 2 // Este programa lee un archivo de texto y muestra cada registro. 3 import java.io.File; 4 import java.io.FileNotFoundException; 5 import java.lang.IllegalStateException; 6 import java.util.NoSuchElementException; 7 import java.util.Scanner; 8 9 import com.deitel.cap17.RegistroCuenta; 10 11 public class LeerArchivoTexto 12 { 13 private Scanner entrada; 14 15 // permite al usuario abrir el archivo 16 public void abrirArchivo() 17 { 18 try 19 { 20 entrada = new Scanner( new File( “clientes.txt” ) ); 21 } // fin de try 22 catch ( FileNotFoundException fileNotFoundException ) 23 { 24 System.err.println( “Error al abrir el archivo.” ); 25 System.exit( 1 ); 26 } // fin de catch 27 } // fin del método abrirArchivo 28 29 // lee registro del archivo 30 public void leerRegistros() 31 { 32 // objeto que se va a escribir en la pantalla 33 RegistroCuenta registro = new RegistroCuenta(); 34 35 System.out.printf( “%-9s%-15s%-18s%10s\n”, “Cuenta”, 36 “Primer nombre”, “Apellido paterno”, “Saldo” ); 37 38 try // lee registros del archivo, usando el objeto Scanner 39 { 40 while ( entrada.hasNext() ) 41 { 42 registro.establecerCuenta( entrada.nextInt() ); // lee el número de cuenta 43 registro.establecerPrimerNombre( entrada.next() ); // lee el primer nombre 44 registro.establecerApellidoPaterno( entrada.next() ); // lee el apellido paterno 45 registro.establecerSaldo( entrada.nextDouble() ); // lee el saldo 46 47 // muestra el contenido del registro 48 System.out.printf( “<%-9d%-15s%-18s%10.2f\n”, 49 registro.obtenerCuenta(), registro.obtenerPrimerNombre(), 50 registro.obtenerApellidoPaterno(), registro.obtenerSaldo() ); 51 } // fin de while 52 } // fin de try 17.4 Archivos de texto de acceso secuencial 735 El método abrirArchivo (líneas 16 a 27) abre el archivo en modo de lectura, creando una instan- cia de un objeto Scanner en la línea 20. Pasamos un objeto File al constructor, el cual especifica que el objeto Scanner leerá datos del archivo “clientes.txt” ubicado en el directorio desde el que se ejecu- ta la aplicación. Si no puede encontrarse el archivo, ocurre una excepción tipo FileNotFoundException. La excepción se maneja en las líneas 22 a 26. Fig. 17.9 � Lectura de un archivo secuencial mediante un objeto Scanner (parte 2 de 2). 53 catch ( NoSuchElementException elementException ) 54 { 55 System.err.println( “El archivo no esta bien formado.” ); 56 entrada.close(); 57 System.exit( 1 ); 58 } // fin de catch 59 catch ( IllegalStateException stateException ) 60 { 61 System.err.println( “Error al leer del archivo.” ); 62 System.exit( 1 ); 63 } // fin de catch 64 } // fin del método leerRegistros 65 66 // cierra el archivo y termina la aplicación 67 public void cerrarArchivo() 68 { 69 if ( entrada != null ) 70 entrada.close(); // cierra el archivo 71 } // fin del método cerrarArchivo 72 } // fin de la clase LeerArchivoTexto Fig. 17.10 � Prueba de la clase LeerArchivoTexto. 1 // Fig. 17.10: PruebaLeerArchivoTexto.java 2 // Este programa prueba la clase LeerArchivoTexto. 3 4 public class PruebaLeerArchivoTexto 5 { 6 public static void main( String[] args ) 7 { 8 LeerArchivoTexto aplicacion = new LeerArchivoTexto(); 9 10 aplicacion.abrirArchivo(); 11 aplicacion.leerRegistros(); 12 aplicacion.cerrarArchivo(); 13 } // fin de main 14 } // fin de la clase PruebaLeerArchivoTexto Cuenta Primer nombre Apellido paterno Saldo 100 Bob Jones 24.98 200 Steve Doe -345.67 300 Pam White 0.00 400 Sam Stone -42.16 500 Sue Rich 224.62 736 Capítulo 17 Archivos, fl ujos y serialización de objetos El método leerRegistros (líneas 30 a 64) lee y muestra registros del archivo. En la línea 33 se crea el objeto RegistroCuenta llamado registro, para almacenar la información del registro actual. En las líneas 35 y 36 se muestran encabezados para las columnas, en los resultados de la aplicación. En las líneas 40 a 51 se leen datos del archivo hasta llegar al marcador de fin de archivo (en cuyo caso, el método has- Next devolverá false en la línea 40). En las líneas 42 a 45 se utilizan los métodos nextInt, next y next- Double de Scanner para recibir un int (el número de cuenta), dos objetos String (el primer nombre y el apellido paterno)y un valor double (el saldo). Cada registro es una línea de datos en el archivo. Los valo- res se almacenan en el objeto registro. Si la información en el archivo no está bien formada (por ejemplo, que haya un apellido paterno en donde debe haber un saldo), se produce una excepción tipo NoSuchEle- mentException al momento de introducir el registro. Esta excepción se maneja en las líneas 53 a 58. Si el objeto Scanner se cerró antes de introducir los datos, se produce una excepción tipo IllegalStateEx- ception (que se maneja en las líneas 59 a 63). Si no ocurren excepciones, la información del registro se muestra en pantalla (líneas 48 a 50). Observe en la cadena de formato de la línea 48 que el número de cuenta, primer nombre y apellido paterno están justificados a la izquierda, mientras que el saldo está justificado a la derecha y se imprime con dos dígitos de precisión. Cada iteración del ciclo introduce una línea de texto del archivo de texto, la cual representa un registro. En las líneas 67 a 71 se define el método cerrarArchivo, el cual cierra el objeto Scanner. El método main se define en la figura 17.10, en las líneas 6 a 13. En la línea 8 se crea un objeto LeerArchivoTexto, el cual se utiliza entonces para abrir, agregar registros y cerrar el archivo (líneas 10 a 12). 17.4.3 Caso de estudio: un programa de solicitud de crédito Para obtener datos secuencialmente de un archivo, por lo general los programas empiezan desde el principio del archivo y leen todos los datos en forma consecutiva, hasta encontrar la información desea- da. Podría ser necesario procesar el archivo secuencialmente varias veces (desde el principio del archivo) durante la ejecución de un programa. La clase Scanner no proporciona la habilidad de reposicionarse hasta el principio del archivo. Si es necesario leer el archivo de nuevo, el programa debe cerrar el archivo y volver a abrirlo. El programa de las figuras 17.11 a 17.13 permite a un gerente de créditos obtener listas de clien- tes con saldos de cero (es decir, los clientes que no deben dinero), saldos con crédito (es decir, los clientes a quienes la compañía les debe dinero) y saldos con débito (es decir, los clientes que deben dinero a la compañía por los bienes y servicios recibidos en el pasado). Un saldo con crédito es un monto negativo, y un saldo con débito es un monto positivo. La enumeración OpcionMenu Empezamos por crear un tipo enum (figura 17.11) para definir las distintas opciones del menú que tendrá el usuario. Las opciones y sus valores se enlistan en las líneas 7 a 10. El método obtenerValor (líneas 19 a 22) obtiene el valor de una constante enum específica. Fig. 17.11 � Enumeración para las opciones del menú del programa de consulta de crédito (parte 1 de 2). 1 // Fig. 17.11: OpcionMenu.java 2 // Enumeración para las opciones del programa de consulta de crédito. 3 4 public enum OpcionMenu 5 { 6 // declara el contenido del tipo enum 7 SALDO_CERO( 1 ), 8 SALDO_CREDITO( 2 ), 9 SALDO_DEBITO( 3 ), 10 FIN( 4 ); 17.4 Archivos de texto de acceso secuencial 737 La clase ConsultaCredito La figura 17.12 contiene la funcionalidad para el programa de consulta de crédito, y la figura 17.13 contiene el método main que ejecuta el programa. Este programa muestra un menú de texto y permite al gerente de créditos introducir una de tres opciones para obtener información sobre un crédito. La opción 1 (SALDO_CERO) muestra las cuentas con saldos de cero. La opción 2 (SALDO_CREDITO) muestra las cuentas con saldos con crédito. La opción 3 (SALDO_DEBITO) muestra las cuentas con saldos con débito. La opción 4 (FIN) termina la ejecución del programa. Fig. 17.11 � Enumeración para las opciones del menú del programa de consulta de crédito (parte 2 de 2). Fig. 17.12 � Programa de consulta de crédito (parte 1 de 4). 1 // Fig. 17.12: ConsultaCredito.java 2 // Este programa lee un archivo secuencialmente y muestra su 3 // contenido con base en el tipo de cuenta que solicita el usuario 4 // (saldo con crédito, saldo con débito o saldo de cero). 5 import java.io.File; 6 import java.io.FileNotFoundException; 7 import java.lang.IllegalStateException; 8 import java.util.NoSuchElementException; 9 import java.util.Scanner; 10 11 import com.deitel.cap17.RegistroCuenta; 12 13 public class ConsultaCredito 14 { 15 private OpcionMenu tipoCuenta; 16 private Scanner entrada; 17 private final static OpcionMenu[] opciones = { OpcionMenu.SALDO_CERO, 18 OpcionMenu.SALDO_CREDITO, OpcionMenu.SALDO_DEBITO, 19 OpcionMenu.FIN }; 20 21 // lee los registros del archivo y muestra sólo los registros del tipo apropiado 22 private void leerRegistros() 23 { 24 // objeto que se va a escribir en el archivo 25 RegistroCuenta registro = new RegistroCuenta(); 11 12 private final int valor; // opción actual del menú 13 14 // constructor 15 OpcionMenu( int valorOpcion ) 16 { 17 valor = valorOpcion; 18 } // fin del constructor del tipo enum OpcionMenu 19 20 // devuelve el valor de una constante 21 public int obtenerValor() 22 { 23 return valor; 24 } // fin del método obtenerValor 25 } // fin del tipo enum OpcionMenu 738 Capítulo 17 Archivos, fl ujos y serialización de objetos Fig. 17.12 � Programa de consulta de crédito (parte 2 de 4). 26 27 try // lee registros 28 { 29 // abre el archivo para leer desde el principio 30 entrada = new Scanner( new File( “clientes.txt” ) ); 31 32 while ( entrada.hasNext() ) // recibe los valores del archivo 33 { 34 registro.establecerCuenta( entrada.nextInt() ); // lee número de cuenta 35 registro.establecerPrimerNombre( entrada.next() ); // lee primer nombre 36 registro.establecerApellidoPaterno( entrada.next() ); // lee apellido paterno 37 registro.establecerSaldo( entrada.nextDouble() ); // lee saldo 38 39 // si el tipo de cuenta es apropiado, muestra el registro 40 if ( debeMostrar( registro.obtenerSaldo() ) ) 41 System.out.printf( “%-10d%-12s%-12s%10.2f\n”, 42 registro.obtenerCuenta(), registro.obtenerPrimerNombre(), 43 registro.obtenerApellidoPaterno(), registro.obtenerSaldo() ); 44 } // fin de while 45 } // fin de try 46 catch ( NoSuchElementException elementException ) 47 { 48 System.err.println( “El archivo no esta bien formado.” ); 49 entrada.close(); 50 System.exit( 1 ); 51 } // fin de catch 52 catch ( IllegalStateException stateException ) 53 { 54 System.err.println( “Error al leer del archivo.” ); 55 System.exit( 1 ); 56 } // fin de catch 57 catch ( FileNotFoundException fileNotFoundException ) 58 { 59 System.err.println( “No se puede encontrar el archivo.” ); 60 System.exit( 1 ); 61 } // fin de catch 62 finally 63 { 64 if ( entrada != null ) 65 entrada.close(); // cierra el objeto Scanner y el archivo 66 } // fin de finally 67 } // fin del método leerRegistros 68 69 // usa el tipo de registro para determinar si el registro debe mostrarse 70 private boolean debeMostrar( double saldo ) 71 { 72 if ( ( tipoCuenta == OpcionMenu.SALDO_CREDITO ) 73 && ( saldo < 0 ) ) 74 return true; 75 76 else if ( ( tipoCuenta == OpcionMenu.SALDO_DEBITO ) 77 && ( saldo > 0 ) ) 78 return true; 17.4 Archivos de texto de acceso secuencial 739 Fig. 17.12 � Programa de consulta de crédito (parte 3 de 4). 79 80 else if ( ( tipoCuenta == OpcionMenu.SALDO_CERO ) 81 && ( saldo == 0 ) ) 82 return true; 83 84 return false; 85 } // fin del método debeMostrar 86 87 // obtiene solicitud del usuario 88 private OpcionMenu obtenerSolicitud() 89 { 90 Scanner textoEnt = new Scanner( System.in); 91 int solicitud = 1; 92 93 // muestra opciones de solicitud 94 System.out.printf( “\n%s\n%s\n%s\n%s\n%s\n”, 95 “Escriba solicitud”, “ 1 - Lista de cuentas con saldos de cero”, 96 “ 2 - Lista de cuentas con saldos con credito”, 97 “ 3 - Lista de cuentas con saldos con debito”, “ 4 - Finalizar ejecucion” ); 98 99 try // trata de recibir la opción del menú 100 { 101 do // recibe solicitud del usuario 102 { 103 System.out.print( “\n? ” ); 104 solicitud = textoEnt.nextInt(); 105 } while ( ( solicitud < 1 ) || ( solicitud > 4 ) ); 106 } // fin de try 107 catch ( NoSuchElementException elementException ) 108 { 109 System.err.println( “Entrada invalida.” ); 110 System.exit( 1 ); 111 } // fin de catch 112 113 return opciones[ solicitud - 1 ]; // devuelve valor de enum para la opción 114 } // fin del método obtenerSolicitud 115 116 public void procesarSolicitudes() 117 { 118 // obtiene la solicitud del usuario (saldo de cero, con crédito o con débito) 119 tipoCuenta = obtenerSolicitud(); 120 121 while ( tipoCuenta != OpcionMenu.FIN ) 122 { 123 switch ( tipoCuenta ) 124 { 125 case SALDO_CERO: 126 System.out.println( “\nCuentas con saldos de cero:\n” ); 127 break; 128 case SALDO_CREDITO: 129 System.out.println( “\nCuentas con saldos con credito:\n” ); 130 break; 740 Capítulo 17 Archivos, fl ujos y serialización de objetos Fig. 17.12 � Programa de consulta de crédito (parte 4 de 4). 131 case SALDO_DEBITO: 132 System.out.println( “\nCuentas con saldos con debito:\n” ); 133 break; 134 } // fin de switch 135 136 leerRegistros(); 137 tipoCuenta = obtenerSolicitud(); 138 } // fin de while 139 } // fin del método procesarSolicitudes 140 } // fin de la clase ConsultaCredito Fig. 17.13 � Prueba de la clase ConsultaCredito. Fig. 17.14 � Salida de ejemplo del programa de consulta de crédito de la figura 17.13 (parte 1 de 2). 1 // Fig. 17.13: PruebaConsultaCredito.java 2 // Este programa prueba la clase ConsultaCredito. 3 4 public class PruebaConsultaCredito 5 { 6 public static void main( String[] args ) 7 { 8 ConsultaCredito aplicacion = new ConsultaCredito(); 9 aplicacion.procesarSolicitudes(); 10 } // fin de main 11 } // fin de la clase PruebaConsultaCredito Escriba solicitud 1 - Lista de cuentas con saldos de cero 2 - Lista de cuentas con saldos con credito 3 - Lista de cuentas con saldos con debito 4 - Finalizar ejecucion ? 1 Cuentas con saldos de cero: 300 Pam White 0.00 Escriba solicitud 1 - Lista de cuentas con saldos de cero 2 - Lista de cuentas con saldos con credito 3 - Lista de cuentas con saldos con debito 4 - Finalizar ejecucion ? 2 Cuentas con saldos con credito: 200 Steve Doe -345.67 400 Sam Stone -42.16 17.4 Archivos de texto de acceso secuencial 741 Para recolectar la información de los registros, se lee todo el archivo completo y se determina si cada uno de los registro cumple o no con los criterios para el tipo de cuenta seleccionado . El método procesarSolicitudes (líneas 116 a 139 de la figura 17.12) llama al método obtenerSolicitud para mostrar las opciones del menú (línea 119), traduce el número introducido por el usuario en un objeto OpcionMenu y almacena el resultado en la variable OpcionMenu llamada tipoCuenta. En las líneas 121 a 138 se itera hasta que el usuario especifique que el programa debe terminar. En las líneas 123 a 134 se muestra un encabezado para imprimir el conjunto actual de registros en la pantalla. En la lí- nea 136 se hace una llamada al método leerRegistros (líneas 22 a 67), el cual itera a través del ar- chivo y lee todos los registros. La línea 30 del método leerRegistros abre el archivo en modo de lectura con un objeto Scanner. El archivo se abrirá en modo de lectura con un nuevo objeto Scanner cada vez que se haga una llamada a este método, para que podamos leer de nuevo desde el principio del archivo. En las líneas 34 a 37 se lee un registro. En la línea 40 se hace una llamada al método debeMostrar (líneas 70 a 85), para determi- nar si el registro actual cumple con el tipo de cuenta solicitado. Si debeMostrar devuelve true, el pro- grama muestra la información de la cuenta. Al llegar al marcador de fin de archivo, el ciclo termina y en la línea 65 se hace una llamada al método close de Scanner para cerrar el objeto Scanner y el archivo. Observe que esto ocurre en un bloque finally, el cual se ejecutará sin importar que se haya leído o no el archivo con éxito. Una vez que se hayan leído todos los registros, el control regresa al método procesarSolicitudes y se hace una llamada otra vez al método obtenerSolicitud (línea 137) para obtener la siguiente opción de menú del usuario. La figura 17.13 contiene el método main, y llama al método procesarSolicitudes en la línea 9. 17.4.4 Actualización de archivos de acceso secuencial En muchos archivos secuenciales, los datos no se pueden modificar sin el riesgo de destruir otros datos en el archivo. Por ejemplo, si el nombre “White” tuviera que cambiarse a “Worthington”, el nombre anterior no podría simplemente sobrescribirse, debido a que el nuevo nombre requiere más espacio. El registro para White se escribió en el archivo como 300 Pam White 0.00 Si el registro se sobrescribe empezando en la misma ubicación en el archivo que utiliza el nuevo nombre, el registro será 300 Pam Worthington 0.00 El nuevo registro es más extenso (tiene más caracteres) que el registro original. Los caracteres más allá de la segunda “o” en “Worthington” sobrescribirán el principio del siguiente registro secuencial Fig. 17.14 � Salida de ejemplo del programa de consulta de crédito de la figura 17.13 (parte 2 de 2). Escriba solicitud 1 - Lista de cuentas con saldos de cero 2 - Lista de cuentas con saldos con credito 3 - Lista de cuentas con saldos con debito 4 - Finalizar ejecucion ? 3 Cuentas con saldos con debito: 100 Bob Jones 24.98 500 Sue Rich 224.62 ? 4 742 Capítulo 17 Archivos, fl ujos y serialización de objetos en el archivo. El problema aquí es que los campos en un archivo de texto (y por ende, los registros) pueden variar en tamaño. Por ejemplo, 7, 14, –117, 2074 y 27383 son todos valores int almacenados en el mismo número de bytes (4) internamente, pero son campos con distintos tamaños cuando se muestran en la pantalla, o se escriben en un archivo como texto. Por lo tanto, los registros en un archi- vo de acceso secuencial comúnmente no se actualizan por partes. En vez de ello, por lo general se sobrescribe todo el archivo. Para realizar el cambio anterior, los registros antes de 300 Pam White 0.00 se copian a un nuevo archivo, se escribe el nuevo registro (que puede tener un tamaño distinto al que está sustituyendo) y se copian los registros después de 300 Pam White 0.00 al nuevo archivo. Es incon- veniente actualizar sólo un registro, pero razonable si una porción substancial de los registros nece- sitan actualización. 17.5 Serialización de objetos En la sección 17.4 demostramos cómo escribir los campos individuales de un objeto RegistroCuen- ta en un archivo como texto, y cómo leer esos campos de un archivo y colocar sus valores en un ob- jeto RegistroCuenta en la memoria. En los ejemplos, se usó RegistroCuenta para agregar la infor- mación de un registro. Cuando las variables de instancia de un objeto RegistroCuenta se enviaban a un archivo en disco, se perdía cierta información, como el tipo de cada valor. Por ejemplo, si se lee el valor “3” de un archivo, no hay forma de saber si el valor proviene de un int, un String o un double. Enun disco sólo tenemos los datos, no la información sobre los tipos. Si el programa que va a leer estos datos “sabe” a qué tipo de objeto corresponden, entonces simplemente se leen y se colocan en objetos de ese tipo. Por ejemplo, en la sección 17.4.2 sabemos que introduciremos un int (el número de cuenta), seguido de dos objetos String (el primer nombre y el apellido paterno) y un double (el saldo). También sabemos que estos valores se separan mediante espacios, y sólo se coloca un registro en cada línea. Algunas veces no sabremos con exactitud cómo se almacenan los datos en un archivo. En tales casos, sería conveniente poder escribir o leer un objeto completo de un archivo. Java cuenta con dicho mecanismo, el cual se conoce como serialización de objetos. Un objeto serializado es un objeto que se representa como una secuencia de bytes, la cual incluye los datos del objeto, así como información acerca del tipo del objeto y los tipos de los datos almacenados en el mismo. Una vez que se escribe un objeto serializado en un archivo, se puede leer de ese archivo y deserializarse; es decir, la información del tipo y los bytes que representan al objeto y sus datos se puede utilizar para recrear el objeto en memoria. Observación de ingeniería de software 17.1 El mecanismo de serialización realiza copias exactas de los objetos. Ésta es una forma sim- ple de clonar objetos sin tener que sobrescribir el método clone de Object. Las clases ObjectInputStream y ObjectOutputStream Las clases ObjectInputStream y ObjectOutputStream, que implementan en forma respectiva a las interfaces ObjectInput y ObjectOutput, permiten leer/escribir objetos completos de/en un flujo (posiblemente un archivo). Para utilizar la serialización con los archivos, inicializamos los objetos ObjectInputStream y ObjectOutputStream con objetos flujo que pueden leer y escribir informa- ción desde/hacia los archivos; objetos de las clases FileInputStream y FileOutputStream, en for- ma respectiva. La acción de inicializar objetos flujo con otros objetos flujo de esta forma se conoce algunas veces como envoltura: el nuevo objeto flujo que se va a crear envuelve al objeto flujo especificado como un argumento del constructor. Por ejemplo, para envolver un objeto File- InputStream en un objeto ObjectInputStream, pasamos el objeto FileInputStream al constructor de ObjectInputStream. 17.5 Serialización de objetos 743 Las interfaces ObjectOutput y ObjectInput La interfaz ObjectOutput contiene el método writeObject, el cual toma un objeto Object como un argumento y escribe su información a un objeto OutputStream. Una clase que implementa a la interfaz ObjectOutput (como ObjectOutputStream) declara este método y se asegura de que el objeto que se va a producir implemente la interfaz Serializable (que veremos en breve). De manera correspondiente, la interfaz ObjectInput contiene el método readObject, el cual lee y devuelve una referencia a un objeto Object de un objeto InputStream. Una vez que se lee un objeto, su referencia puede convertirse en el tipo actual del objeto. Como veremos en el capítulo 27, las aplicaciones que se comunican a través de una red (como Internet) también pueden transmitir objetos completos a través de la red. 17.5.1 Creación de un archivo de acceso secuencial mediante el uso de la serialización de objetos En esta sección y en la sección 17.5.2 vamos a crear y manipular archivos de acceso secuencial, usando la serialización de objetos. La serialización de objetos que mostraremos aquí se realiza mediante flujos basa- dos en bytes, de manera que los archivos secuenciales que se creen y manipulen serán archivos binarios. Recuerde que, por lo general, los archivos binarios no se pueden ver en los editores de texto estándar. Por esta razón, escribimos una aplicación separada que sabe cómo leer y mostrar objetos serializados. Empe- zaremos por crear y escribir objetos serializados a un archivo de acceso secuencial. El ejemplo es similar al de la sección 17.4, por lo que sólo nos enfocaremos en las nuevas características. Definición de la clase RegistroCuentaSerializable Para empezar, modificaremos nuestra clase RegistroCuenta de manera que los objetos de esta clase puedan serializarse. La clase RegistroCuentaSerializable (figura 17.15) implementa a la interfaz Serializable (línea 7), la cual permite serializar y deserializar los objetos de la clase Registro- CuentaSerializable con objetos ObjectOutputStream y ObjectInputStream, respectivamente. La interfaz Serializable es una interfaz de marcado. Dicha interfaz no contiene métodos. Una clase que implementa a Serializable se marca como objeto Serializable. Esto es importante, ya que un objeto ObjectOutputStream no enviará un objeto como salida a menos que sea un objeto Seria- lizable, lo cual es el caso para cualquier objeto de una clase que implemente a Serializable. Fig. 17.15 � La clase RegistroCuentaSerializable para los objetos serializables (parte 1 de 3). 1 // Fig. 17.15: RegistroCuentaSerializable.java 2 // La clase RegistroCuentaSerializable para objetos serializables. 3 package com.deitel.cap17; // empaquetada para reutilizarla 4 5 import java.io.Serializable; 6 7 public class RegistroCuentaSerializable implements Serializable 8 { 9 private int cuenta; 10 private String primerNombre; 11 private String apellidoPaterno; 12 private double saldo; 13 14 // el constructor sin argumentos llama al otro constructor con valores predeterminados 15 public RegistroCuentaSerializable() 16 { 17 this( 0, “”, “”, 0.0 ); 18 } // fin del constructor de RegistroCuentaSerializable sin argumentos 744 Capítulo 17 Archivos, fl ujos y serialización de objetos Fig. 17.15 � La clase RegistroCuentaSerializable para los objetos serializables (parte 2 de 3). 19 20 // el constructor con cuatro argumentos inicializa un registro 21 public RegistroCuentaSerializable( 22 int cta, String nombre, String apellido, double sal ) 23 { 24 establecerCuenta( cta ); 25 establecerPrimerNombre( nombre ); 26 establecerApellidoPaterno( apellido ); 27 establecerSaldo( sal ); 28 } // fin del constructor de RegistroCuentaSerializable con cuatro argumentos 29 30 // establece el número de cuenta 31 public void establecerCuenta( int cta ) 32 { 33 cuenta = cta; 34 } // fin del método establecerCuenta 35 36 // obtiene el número de cuenta 37 public int obtenerCuenta() 38 { 39 return cuenta; 40 } // fin del método obtenerCuenta 41 42 // establece el primer nombre 43 public void establecerPrimerNombre( String nombre ) 44 { 45 primerNombre = nombre; 46 } // fin del método establecerPrimerNombre 47 48 // obtiene el primer nombre 49 public String obtenerPrimerNombre() 50 { 51 return primerNombre; 52 } // fin del método obtenerPrimerNombre 53 54 // establece el apellido paterno 55 public void establecerApellidoPaterno( String apellido ) 56 { 57 apellidoPaterno = apellido; 58 } // fin del método establecerApellidoPaterno 59 60 // obtiene el apellido paterno 61 public String obtenerApellidoPaterno() 62 { 63 return apellidoPaterno; 64 } // fin del método obtenerApellidoPaterno 65 66 // establece el saldo 67 public void establecerSaldo( double sal ) 68 { 69 saldo = sal; 70 } // fin del método establecerSaldo 71 17.5 Serialización de objetos 745 En una clase Serializable, cada variable de instancia debe ser Serializable. Cualquier variable de instancia que no sea serializable debe declararse como transient, para indicar que debe ignorarse durante el proceso de serialización. De manera predeterminada, todas las variables de tipos primitivos son serializables. Para las variables de tipos de referencias, hay que verificar la documentación de la clase (y posiblemente de sus superclases) para asegurar que el tipo sea Serializable. Por ejemplo, los objetos Stringson Serializable. De manera predeterminada, los arreglos son serializables; no obstante, en un arreglo de tipo por referencia, los objetos referenciados tal vez no lo sean. La clase RegistroCuentaSerializable contiene los miembros de datos private llamados cuenta, primer- Nombre, apellidoPaterno y saldo; todos ellos son Serializable. Esta clase también proporciona métodos public establecer y obtener para acceder a los campos private. Escritura de objetos serializados en un archivo de acceso secuencial Ahora hablaremos sobre el código que crea el archivo de acceso secuencial (figuras 17.16 y 17.17). Aquí nos concentraremos sólo en los nuevos conceptos. Como dijimos en la sección 17.2, un programa puede abrir un archivo creando un objeto de las clases de flujo FileInputStream o FileOuptutStream. En este ejemplo, el archivo se abrirá en modo de salida, por lo que el programa crea un objeto Fi- leOutputStream (línea 21 de la figura 17.16). El argumento String que se pasa al constructor de FileOutputStream representa el nombre y la ruta del archivo que se va a abrir. Los archivos existentes que se abren en modo de salida de esta forma se truncan. Elegimos la extensión de archivo .ser para los archivos binarios que contienen objetos serializados, pero esto no es obligatorio. Error común de programación 17.2 Es un error lógico abrir un archivo existente en modo de salida cuando, de hecho, el usua- rio desea preservar ese archivo. La clase FileOutputStream cuenta con un constructor sobrecargado que nos permite abrir un archivo y adjuntar datos al final del mismo. Esto preserva el contenido del archivo. Fig. 17.15 � La clase RegistroCuentaSerializable para los objetos serializables (parte 3 de 3). Fig. 17.16 � Archivo secuencial creado mediante ObjectOutputStream (parte 1 de 3). 72 // obtiene el saldo 73 public double obtenerSaldo() 74 { 75 return saldo; 76 } // fin del método obtenerSaldo 77 } // fin de la clase RegistroCuentaSerializable 1 // Fig. 17.16: CrearArchivoSecuencial.java 2 // Escritura de objetos en forma secuencial a un archivo, con la clase ObjectOutputStream. 3 import java.io.FileOutputStream; 4 import java.io.IOException; 5 import java.io.ObjectOutputStream; 6 import java.util.NoSuchElementException; 7 import java.util.Scanner; 8 9 import com.deitel.cap17.RegistroCuentaSerializable; 10 11 public class CrearArchivoSecuencial 12 { 13 private ObjectOutputStream salida; // envía los datos a un archivo 746 Capítulo 17 Archivos, fl ujos y serialización de objetos 14 15 // permite al usuario especificar el nombre del archivo 16 public void abrirArchivo() 17 { 18 try // abre el archivo 19 { 20 salida = new ObjectOutputStream( 21 new FileOutputStream( “clientes.ser” ) ); 22 } // fin de try 23 catch ( IOException ioException ) 24 { 25 System.err.println( “Error al abrir el archivo.” ); 26 } // fin de catch 27 } // fin del método abrirArchivo 28 29 // agrega registros al archivo 30 public void agregarRegistros() 31 { 32 RegistroCuentaSerializable registro; // objeto que se va a escribir al archivo 33 int numeroCuenta = 0; // número de cuenta para el objeto registro 34 String primerNombre; // primer nombre para el objeto registro 35 String apellidoPaterno; // apellido paterno para el objeto registro 36 double saldo; // saldo para el objeto registro 37 38 Scanner entrada = new Scanner( System.in ); 39 40 System.out.printf( “%s\n%s\n%s\n%s\n\n”, 41 “Para terminar de introducir datos, escriba el indicador de fin de archivo”, 42 “Cuando se le pida que introduzca los datos.”, 43 “En UNIX/Linux/Mac OS X escriba <ctrl> d y oprima Intro”, 44 “En Windows escriba <ctrl> z y oprima Intro” ); 45 46 System.out.printf( "%s\n%s", 47 “Escriba el numero de cuenta (> 0), primer nombre, apellido y saldo.”, 48 “? ” ); 49 50 while ( entrada.hasNext() ) // itera hasta el indicador de fin de archivo 51 { 52 try // envía los valores al archivo 53 { 54 numeroCuenta = entrada.nextInt(); // lee el número de cuenta 55 primerNombre = entrada.next(); // lee el primer nombre 56 apellidoPaterno = entrada.next(); // lee el apellido paterno 57 saldo = entrada.nextDouble(); // lee el saldo 58 59 if ( numeroCuenta > 0 ) 60 { 61 // crea un registro nuevo 62 registro = new RegistroCuentaSerializable( numeroCuenta, 63 primerNombre, apellidoPaterno, saldo ); 64 salida.writeObject( registro ); // envía el registro como salida 65 } // fin de if Fig. 17.16 � Archivo secuencial creado mediante ObjectOutputStream (parte 2 de 3). 17.5 Serialización de objetos 747 Fig. 17.16 � Archivo secuencial creado mediante ObjectOutputStream (parte 3 de 3). 66 else 67 { 68 System.out.println( 69 “El numero de cuenta debe ser mayor de 0.” ); 70 } // fin de else 71 } // fin de try 72 catch ( IOException ioException ) 73 { 74 System.err.println( “Error al escribir en el archivo.” ); 75 return; 76 } // fin de catch 77 catch ( NoSuchElementException elementException ) 78 { 79 System.err.println( “Entrada invalida. Intente de nuevo.” ); 80 entrada.nextLine(); // descarta la entrada para que el usuario intente de nuevo 81 } // fin de catch 82 83 System.out.printf( “%s %s\n%s”, “Escriba el numero de cuenta (>0),”, 84 “primer nombre, apellido y saldo.”, “? ” ); 85 } // fin de while 86 } // fin del método agregarRegistros 87 88 // cierra el archivo y termina la aplicación 89 public void cerrarArchivo() 90 { 91 try // cierra el archivo 92 { 93 if ( salida != null ) 94 salida.close(); 95 } // fin de try 96 catch ( IOException ioException ) 97 { 98 System.err.println( “Error al cerrar el archivo.” ); 99 System.exit( 1 ); 100 } // fin de catch 101 } // fin del método cerrarArchivo 102 } // fin de la clase CrearArchivoSecuencial Fig. 17.17 � Prueba de la clase CrearArchivoSecuencial (parte 1 de 2). 1 // Fig. 17.17: PruebaCrearArchivoSecuencial.java 2 // Prueba de la clase CrearArchivoSecuencial. 3 4 public class PruebaCrearArchivoSecuencial 5 { 6 public static void main( String[] args ) 7 { 8 CrearArchivoSecuencial aplicacion = new CrearArchivoSecuencial(); 9 10 aplicacion.abrirArchivo(); 11 aplicacion.agregarRegistros(); 748 Capítulo 17 Archivos, fl ujos y serialización de objetos La clase FileOutputStream cuenta con métodos para escribir arreglos tipo byte y objetos byte in- dividuales en un archivo, pero nosotros queremos escribir objetos en un archivo. Por esta razón, envolve- mos un objeto FileOutputStream en un objeto ObjectOutputStream, pasando el nuevo objeto Fi- leOutputStream al constructor de ObjectOutputStream (líneas 20 y 21). El objeto ObjectOutputStream utiliza al objeto FileOutputStream para escribir objetos en el archivo. En las líneas 20 y 21 se podría lanzar una excepción tipo IOException si ocurre un problema al abrir el archivo (por ejemplo, cuando se abre un archivo para escribir en una unidad de disco con espacio insuficiente, o cuando se abre un archi- vo de sólo lectura para escribir datos). Si es así, el programa muestra un mensaje de error (líneas 23 a 26). Si no ocurre una excepción, el archivo se abre y se puede utilizar la variable salida para escribir objetos en el archivo. Este programa asume que los datos se introducen de manera correcta y en el orden de número de registro apropiado. El método agregarRegistros (líneas 30 a 86) realiza la operación de escritura. En las líneas
Compartir