Jerarquía de Clases en Java
Jerarquía de Clases en Java
Jerarquía de Clases en Java
En la jerarqua de clases de la figura aparecen varias clases (faltan algunas). Las ms importantes son java.io.FileInputStream, java.io.ObjectInputStream y java.io.FilterInputStream. La primera permite leer bytes a partir del flujo de entrada obtenido de un archivo; la segunda permite deserializar datos de tipos primitivos y objetos serializados antes usando java.io.ObjectOutputStream java.io.FilterInputStream es una clase mucho ms misteriosa que las otras: es un decorador. Para no interrumpir la exposicin de las clases elementales de E/S, el patrn observador se explicar ms adelante. Por ahora, es suficiente con saber que las subclases de FilterInputStream proporcionan nuevas funciones a las clases a las cuales "decoran" o "envuelven".Las instancias de las subclases de FilterInputStream (en la figura faltan java.io.LineNumberInputStream y java.io.PushbackInputStream) permiten la lectura de un flujo y la escritura en otro, alterando los datos en el paso de un flujo a otro. Pueden usarse para almacenar datos en buffers, leer tipos primitivos retroceder hacia atrs en un flujo, etc; adems, pueden combinarse de modo que la salida de una instancia sea la entrada de otra.
Un BufferedInputStream usa un array interno de almacenamiento temporal (buffer) para el flujo de entrada. Cuando se crea un BufferedInputStream, se creatambin un array interno de almacenamiento de bytes. Cada vez que se llama a un mtodo read(), los bytes se leen del array de almacenamiento; segn se van leyendo bytes, el array se vuelve a rellenar con bytes del flujo de entrada, ledos de golpe. As se evita tener que leer y almacenar byte a byte (y llamar cada vez a los mtodos nativos del sistema operativo sobre el cual trabaja la mquina virtual Java), lo cual mejora mucho el rendimiento de la E/S. Siempre que se pueda, interesa manejar clases de E/S que trabajen con buffers. Analizemos, como ejemplo, el siguiente cdigo: BufferedInputStream bis = new BufferedInputStream(objetoInputStream, 1024) bis.read() Con el constructor, se ha creado un buffer de 1024 bytes de tamao. Al llamar por primera vez a read(), se intentar llenar por completo el buffer a partir del flujo de entrada. En las siguientes llamadas, se leer directamente del buffer, que se ir rellenando, cuando convenga, con los bytes del flujo de entrada. DataInputStream incorpora mtodos para leer bytes y arrays de bytes, al igualque InputStream; pero adems incluye mtodos para leer caracteres, Strings, nmeros (enteros, de punto flotante...), etc. Todos los mtodos empiezan con read: readBoolean(), readByte(), readChar()..., excepto skipBytes()
A continuacin se expone un programa de ejemplo del uso combinado de BufferedInputStream y FileInputStream, el cual permite leer un archivo de texto (situado en el directorio donde se ejecuta la clase) y mostrar su contenido en pantalla: LeerCaracter.java
Si el lector prescinde del decorador BufferedOutputStream y decide usar directamente el mtodo write() de la clase FileOutputStream, notar cuando use valores elevados de N la diferencia de rendimiento ocasionada por no usar buffers.
La jerarqua de clases java.io.OutputStream La superclase raz java.io.OutputStream proporciona los mtodos bsicos para escribir bytes en un flujo de salida basado en bytes (todos ellos son bloqueantes): public abstract void write(int byte) throws IOException public void write(byte[] datos) throws IOException public void write(byte[] datos, int offset, int longitud) throws IOException El primer mtodo escribe un nico byte en un flujo de salida. El segundo escribe un array de bytes, y el tercero escribe longitud bytes del array datos, comenzando por la posicin indicada por el entero offset. Los sistemas operativos utilizan buffers internos para evitar tener que escribir los bytes de uno en uno. As pueden escribir decenas o cientos de bytes de golpe, lo cual redunda en un mejor rendimiento del sistema. Esta clase dispone de un mtodo (public void flush() throws IOException) que obliga a escribir todos los bytes que haya en el buffer, est lleno o no. El tamao exacto de cada buffer depende del sistema operativo usado y de la implementacin de la mquina virtual Java. OutputStream tambin dispone de un mtodo public void close() throws IOException, que se encarga de cerrar el flujo de salida y de liberar los recursos del sistema usados por el flujo. System.out (el flujo de entrada estndar) y System.err (el flujo de salida estndar de los errores) son objetos OutputStream. Ms especficamente, son objetos PrintStream.
aparece en la figura aparecen varias clases (faltan algunas). Las ms importantes son java.io.FileOutputStream, java.io.ObjectOutputStream y java.io.FilterOutputStream. La primera permite escribir bytes en el flujo de salida asociado a un archivo; la segunda permite serializar datos de tipos primitivos y objetos. Como era de esperar, FilterOutputStream es un decorador. Las instancias de las subclases de FilterOutputStream (en la figura faltan java.io.LineNumberOutputStream y java.io.PushbackOutputStream) permiten decorar los objetos a los que envuelven. DataOutputStream incorpora mtodos para escribir bytes y arrays de bytes, al igual que OutputStream; pero adems incluye mtodos para escribir caracteres, Strings, nmeros (enteros, de punto flotante...), etc. Todos los mtodos empiezan con write: writeBoolean(), writeByte(), writeChar()...,excepto flush().
FileOutputStream complementa a FileInputStream. Con respecto a esta ltima, presenta una diferencia: sus constructores, a diferencia de los de FileInputStream, no arrojan excepciones del tipo FileNotFoundException; si el archivo no existe, FileOutputStream lo crea. En caso de que el archivo s exista y se utilice el constructor que aparece en la siguiente lnea de cdigo: FileOutputStream fos = new FileOutputStream(fichero); hay que tener en cuenta que, con la primera llamada a write(), los nuevos datos se escribirn sobre los que ya tena el archivo, con su consiguiente prdida. Si se desea que los nuevos datos se aadan tras los ya existentes, se deber usar este constructor: FileOutputStream fos = new FileOutputStream(fichero, true); La clase PrintStream incluye varios mtodos public void print() y public void println() para imprimir (en el sentido de mostrar al usuario por la salida estndar) cualquier valor de un objeto o un tipo primitivo .Los mtodos print() convierten el argumento en un String y luego lo
transforman en bytes de acuerdo con la codificacin por defecto del sistema; despus estos bytes se escriben del modo descrito para los mtodos write().Los mtodos println() hacen exactamente lo mismo que los print(), pero incluyen un carcter de nueva lnea ("\n", "r" o "r\n"; depende de la plataforma).Los objetos PrintStream presentan una curiosa propiedad con respecto a lasdems clases de E/S: no lanzan excepciones. El motivo para este comportamiento reside, probablemente, en que tal como ya se dijo, System.out (el flujo de entrada estndar) y System.err (el flujo de salida estndar de los errores) son objetos PrintStream. Sera bastante incmodo para un programador tener que escribir cdigo para manejar las excepciones cada vez que escriba lneas inofensivas como System.out.println("JAVA"). Que esta clase no lance excepciones no quiere decir que no las pueda producir; lo que ocurre es que las gestiona internamente. Para comprobar si se ha producido algn error se llama al mtodo public boolean checkError(). Un objeto BufferedOutputStream puede decorar a un OutputStream, dndole la posibilidad de que los bytes ledos se almacenen en un buffer y se escriban cuando el buffer est lleno (salvo que se use flush(), que fuerza a escribir, est o no lleno). Su funcionamiento no difiere mucho del correspondiente a la clase BufferedInputStream, vista en el subapartado anterior: las llamadas a write() van almacenando los datos en el buffer, que slo se escribir cuando est lleno (o se llame a flush()). A continuacin, se muestra un ejemplo del uso combinado de las clases BufferedOutputStream y FileOutputStream, el cual escribe N veces las letras A y B en un archivo. Escribir Caracter
La clase ObjectOutputStream es una clase muy importante para las Comunicaciones en red: permite serializar objetos (ObjectInputStream permite deserializarlos). Serializar es la accin de codificar un objeto en un flujo de bytes; deserializar es la accin de decodificar un flujo de bytes para reconstruir una copia del objeto original. Esencialmente, serializar un objeto equivale a guardar (y luego poder cargar) el estado de un objeto. La serializacin es un mecanismo de implementacin de la persistencia de objetos. Uso persistencia (de un objeto) en el sentido de capacidad de un objeto para persistir
en el tiempo y en el espacio, independientemente de la MVJ que lo cre. El flujo de bytes que produce la serializacin de un objeto se puede enviar a mquinas remotas mediante sockets o se puede guardar en un archivo. Para poder reconstruir correctamente un objeto, el proceso de serializacin tambin almacena en el flujo de bytes la descripcin de la clase a la que pertenece el objeto. Bruce Eckel explica as la serializacin en Java (Thinking in Java 3rd Edition): La serializacin de objetos en Java le permite tomar cualquier objeto que
implemente la interfaz Serializable y convertirlo en una secuencia de bytes que puede restaurarse completamente ms tarde para regenerar el objeto original. Esto es cierto incluso a travs de una red, lo que significa que el mecanismo de la serializacin compensa automticamente las diferencias entre sistemas operativos. Esto es, puede crear un objeto en una mquina Windows, serializarlo y enviarlo a travs de la red a una mquina Unix donde ser reconstruido correctamente. No tiene que preocuparse sobre las representaciones de los datos en las diferentes mquinas, el orden de los bytes o cualquier otro detalle. Por s misma, la serializacin de objetos es interesante porque le permite implementar persistencia ligera. Recuerde que la persistencia significa que el tiempo de vida de un objeto no est determinado por si un programa se est ejecutando; el objeto vive entre las invocaciones del programa. Tomando un objeto serializable y escribindolo en el disco, y entonces restaurando ese objeto cuando el programa es reinvocado, puede producir el efecto de persistencia. El motivo por el que se llama ligera es que no puede simplemente definir un objeto y usar alguna clase de palabra reservada persistent y dejar que el sistema se preocupe de los detalles (aunque esto podra ocurrir en el futuro). En lugar de eso, debe explcitamente serializar y deserializar los objetos en su programa. Una caracterstica sumamente interesante de la
serializacin estriba en su capacidad de congelar objetos vinculados y de hacer que retornen a su estado original, aunque la mquina de destino est a miles de kilmetros de la maquina donde se crearon originalmente los objetos. Cuando se serializa un objeto, se serializan tambin todos los objetos a los que tenga referencias (los cuales deben, por tanto, implementar tambin la interfaz Serializable); como es lgico, al deserializarlo se reconstruyen tambin los objetos vinculados. Esta propiedad abre posibilidades muy interesantes para los programadores: mareas enteras de objetos interconectados de muchas maneras pueden almacenarse y recuperarse cuando se necesiten. Si se intentar simular la serializacin de un objeto mediante la clase DataOutputStream, seprecisara guardar cada dato de tipos simples (int, double, float...) contenido en el objeto, as como los datos de tipos simples que contuvieran los objetos a los cuales contiene referencias. Se puede trabajar as, pero sera tarea muy tediosa y propensa a errores.Cuando se deserializa un flujo de bytes, se llama al mtodo protected Class resolveClass(ObjectStreamClass descripcion) throws IOException, ClassNotFoundException de la clase ObjectInputStream para que cargue dinmicamente los bytecodes de las clases cuyas descripciones encuentra en el flujo (suponiendo que no los hubiera cargado antes). Este mtodo llama al cargador de clases de Java, el cual es el verdadero encargado de buscar los archivos .class necesarios y de cargar dinmicamente sus bytecodes. El cargador de clases busca, en primer lugar, en el directorio actual (aquel desde el cual se ejecut la aplicacin), y si no los encuentra sigue buscando en los directorios indicados en el CLASSPATH. Si finalmente no encuentra los .class que se necesitan, lanza
una excepcin java.lang.ClassNotFoundException. Siempre interesa configurar el CLASSPATH local para que incluya todos los directorios donde estn los archivos .class de las clases que se necesitarn durante el proceso de deserializacin, pues no es habitual que estn todos en el directorio desde el cual se lanza la aplicacin. La mejor manera de ver cmo funciona la serializacin es mediante un ejemplo completo. En l veremos cmo se graban objetos Persona en un archivo llamado personas.txt (si no existe, se crea), ubicado en el directorio donde se ejecuta EscribirPersona.
Escribir Persona
public String toString() { return ("Nombre: " + nombre +"; Edad: " + edad); } }
Persona implementa la interfaz Serializable porque, tal como se dijo cualquier objeto que pueda ser serializado (y deserializado) debe implementarla. Leer Persona
} }
Para que el cdigo funcione correctamente, se necesita ejecutar LeerPersona desde el mismo directorio donde se ejecuta EscribirPersona o configurar elCLASSPATH para que LeerPersona tenga acceso al archivo Persona.class (generado al compilar la clase EscribirPersona), y despus de ejecutar esta ltima al menos una vez.
Jerarqua de Clases (sigue) No hay ningn obstculo para serializar a travs de redes. A continuacin incluy el cdigo de la versin de red correspondiente al ejemplo anterior Tal y como est escrito, se considera que el cliente y el servidor se ejecutan en la misma mquina. Si se desea ejecutar EscribirPersonaRed en una mquina distinta de aquella donde se ejecuta LeerPersonaRed, habr que modificar la lnea Socket socket = new Socket("localhost", 9000); y escribir, en lugar de localhost, el nombre de la mquina donde se vaya a ejecutar LeerPersonaRed. Adems, ser necesario colocar el archivo Persona.class en esta ltima mquina de manera que LeerPersonaRed pueda acceder a ella cuando se ejecute.
EscribirPersonaRed.java
Persona.java
LeerPersonaRed.java