No es un bug, es una característica no documentada

lunes, 12 de octubre de 2015

Acceso a datos. Persistencia

0:22 Posted by Inazio , No comments

Persistencia


Es la capacidad de los objetos para perdurar más allá de la duración del programa que los creó.

Se trata de que un objeto pueda mantener su estado entre diferentes ejecuciones del programa.

Una de las formas que existen de implementar la persistencia es almacenar los objetos en memoria secundaria antes de que el programa termine, y cargarlos de nuevo en memoria primaria en la siguiente ejecución.

Serialización


Al implementar la persistencia, es frecuente que el estado de los objetos se almacene en un archivo.

El archivo es una sucesión de bytes. Por tanto, es necesario “traducir” el estado del objeto a una sucesión lineal de bytes. Ese proceso se conoce como serialización.



La serialización de objetos permite convertir cualquier objeto que implemente a la interfaz Serializable o a la interfaz Externalizable en una secuencia de bites que puede ser utilizada posteriormente para reconstruir el objeto original.

Esta secuencia de bits puede guardarse en un fichero o puede enviarse a otra máquina virtual (que puede estar ejecutándose en otro sistema operativo) para reconstruir el objeto (deserialización) en otro instante o en otra máquina virtual. No tenemos que preocuparnos en absoluto de las diferentes representaciones de datos en distintos ordenadores.

Los objetos mantienen referencias a otros objetos. Estos otros objetos deben ser también almacenados y recuperados con el fin de mantener las relaciones originales. Por supuesto, todos estos objetos deben ser serializables ya que de lo contrario se lanzará una excepción del tipo NotSerializableException.

Para reconstruir un objeto (o conjunto de objetos) Java serializado es necesario que la clase (o clases) esté en el classpath con el fin de identificarla y verificarla antes de restaurar el contenido en una nueva instancia.

Ejemplo

import java.util.*;
import java.io.*;

public class Serial {

     public static void main(String[] args) {
          try{
                FileOutputStream archivo = new FileOutputStream("D:\\prueba.dat");
                ObjectOutputStream salida = new ObjectOutputStream(archivo);
                salida.writeObject("Hoy es: ");
                salida.writeObject(new Date());
                salida.close();
          }
          catch(IOException e){
                System.out.println("Problemas con el archivo");
          }
         
          try{
                FileInputStream archivo = new FileInputStream("D:\\prueba.dat");
                ObjectInputStream entrada = new ObjectInputStream(archivo);
                String hoy = (String)entrada.readObject();
                Date fecha = (Date)entrada.readObject();
                entrada.close();
                System.out.println(hoy + fecha);
          }
          catch(FileNotFoundException e){
                System.out.println("No se pudo abrir el archivo");
          }
          catch(IOException e){
                System.out.println("Problemas con el archivo");
          }
          catch(Exception e){
                System.out.println("Error al leer un objeto");
          }

     }
}

En la salida del programa veríamos algo como

Hoy es: Sun Oct 11 18:09:10 CEST 2015

Un objeto se puede serializar si implementa el interface Serializable. Este interface no declara ningún método, se trata de un interface vacío.

import java.io.*;
public interface Serializable{}

Para que un objeto sea serializable, todas sus variables de instancia han de ser serializables. Todos los tipos primitivos en Java son serializables por defecto (igual que los arrays y otros muchos tipos estándar).

La serialización se introdujo en Java para implementar la persistencia y soportar la Invocación Remota de Métodos (RMI) que permite a una aplicación enviar mensajes a un objeto remoto (que se está ejecutando en otra máquina virtual). También es necesaria en el caso de los JavaBeans.

Flujos para entrada y salida de objetos



Los flujos para leer y escribir objetos tienen métodos que reciben o devuelven instancias de la clase Object.


void ObjectOutputStream.writeObject(Object o)

Serializa un objeto y lo graba en el flujo al que se haya conectado el ObjectOutputStream

Object ObjectInputStream.readObject()

Carga el siguiente objecto del flujo al que esté conectado. Para usarlo hay que hacer un cast. Ejemplo:

Perro p = (Perro) miStream.readObject();

El modificador Transient


Cuando un dato de una clase contiene información sensible, hay disponibles varias técnicas para protegerla. Incluso cuando dicha información es privada (el miembro dato tiene el modificador private) una vez que se ha enviado al flujo de salida alguien puede leerla en el archivo en disco o interceptarla en la red. El modo más simple de proteger la información sensible, como una contraseña (password) es la de poner el modificador transient delante del miembro dato que la guarda. En el siguiente ejemplo se redefine la función toString() método de la clase base Object. Esta función devolverá el nombre del cliente y la contraseña. En el caso de que la variable password guarde el valor null se imprimirá el texto (no disponible).

Nota: Probar a eliminar el modificador transient, comprobar que se pierde la privacidad al almacenarse la clave tal cual, sin null.

ARCHIVO CLIENTE.JAVA


public class Cliente implements java.io.Serializable{

     // Propiedades
     private String nombre;
     private transient String passWord;
    
     // Constructor
     public Cliente(String nombre, String pw){
          this.nombre = nombre;
          passWord = pw;
     }
    
     // Métodos
     public String toString(){
          String texto = (passWord==null) ? "(No disponible)" : passWord;
          texto += nombre;
          return texto;
     }
}

ARCHIVO SERIALIZABLE.JAVA

import java.io.*;

public class Serializacion {

     public static void main(String[] args) {
         
          // Propiedades
          Cliente cliente = new Cliente("Inazio", "DAM");
         
          // Código
          try{
                ObjectOutputStream salida = new ObjectOutputStream(new FileOutputStream("D:\\cliente.obj"));
                salida.writeObject("Datos del cliente\n");
                salida.writeObject(cliente);
                salida.close();
               
                ObjectInputStream entrada = new ObjectInputStream(new FileInputStream("D:\\cliente.obj"));
                String str = (String)entrada.readObject();
                Cliente obj1 = (Cliente)entrada.readObject();
                System.out.println("------------");
                System.out.println(str + " " + obj1);
                System.out.println("------------");
                entrada.close();
          }
          catch(IOException e){
                e.printStackTrace();
          }
          catch(ClassNotFoundException e){
                e.printStackTrace();
          }
         
          try{
                // Espera la pulsación de una tecla y luego RETORNO
                System.in.read();
          }
          catch(Exception e){}

     }

}

Así, la salida del programa será

------------
Datos del cliente
 (No disponible)Inazio
------------

La herencia en objetos serializables


Para serializar objetos de una jerarquía solamente la clase base tiene que implementar el interface Serializable. En el siguiente ejemplo se muestra una herencia de la clase Figura que implementa la clase Serializable. De esta clase se heredan la clase Circulo y Rectangulo, ninguna de ellas implementa la clase Serializable.

ARCHIVO FIGURA.JAVA

public abstract class Figura implements java.io.Serializable{

     // Propiedades
     protected int x;
     protected int y;
    
     // Constructor
     public Figura(int x, int y){
          this.x = x;
          this.y = y;
     }
    
     // Métodos
     public abstract double area();
}

ARCHIVO CIRCULO.JAVA

public class Circulo extends Figura{

     // Propiedades
     protected double radio;
     private static final double PI = 3.1416;
    
     // Constructor
     public Circulo(int x, int y, double radio){
          super(x, y);
          this.radio = radio;
     }
    
     // Métodos
     public double area(){
          return PI*radio*radio;
     }
}

ARCHIVO RECTANGULO.JAVA

public class Rectangulo extends Figura{

     // Propiedades
     protected double ancho, alto;
    
     // Constructor
     public Rectangulo(int x, int y, double ancho, double alto){
          super(x, y);
          this.ancho = ancho;
          this.alto = alto;
     }
    
     // Métodos
     public double area(){
          return ancho*alto;
     }
}

ARCHIVO HERENCIASERIALIZABLE.JAVA

import java.io.*;

public class HerenciaSerializable {

     public static void main(String[] args) {
          // Propiedades
          Figura fig1 = new Rectangulo(10, 15, 30, 60);
          Figura fig2 = new Circulo(12, 19, 60);
         
          // Código
          try{
                ObjectOutputStream salida = new ObjectOutputStream(new FileOutputStream("D:\\figura.obj"));
                salida.writeObject("Guardar un objeto de una clase derivada\n");
                salida.writeObject(fig1);
                salida.writeObject(fig2);
                salida.close();
               
                ObjectInputStream entrada = new ObjectInputStream(new FileInputStream("D:\\figura.obj"));
                String str = (String)entrada.readObject();
                Figura obj1 = (Figura)entrada.readObject();
                Figura obj2 = (Figura)entrada.readObject();
                System.out.println("-.-.-.-.-.-.-.-.-.-.-");
                System.out.println(obj1.getClass().getName() + " origen (" + obj1.x + ", " + obj1.y + ")" + " area= " + obj1.area());
                System.out.println(obj2.getClass().getName() + " origen (" + obj2.x + ", " + obj2.y + ")" + " area=" + obj2.area());
                System.out.println("-.-.-.-.-.-.-.-.-.-.-");
                entrada.close();
                // Se puede fundir en un catch Exception
          }
          catch(IOException e){
                e.printStackTrace();
          }
          catch(ClassNotFoundException e){
                e.printStackTrace();
          }
         
          try{
                // Espera la pulsación de una tecla y luego RETORNO
                System.in.read();
          }
          catch(Exception e){}

     }

}

Y el resultado mostrado por pantalla es

-.-.-.-.-.-.-.-.-.-.-
Rectangulo origen (10, 15) area= 1800.0
Circulo origen (12, 19) area=11309.76
-.-.-.-.-.-.-.-.-.-.-

La interfaz Externalizable


Para un control explícito del proceso de serialización la clase ha de implementar el interface Externalizable. La clase es responsable de escribir y de leer su contenido, y ha de estar coordinada con sus clases base para hacer esto. La definición del interior Externalizable es la siguiente:

package java.io;
public interface Externalizable extends Serializabe{

public void writeExternal(ObjectOutput out) throws IOException;
public void readExternal(ObjectOutput in) throws IOException, java.lang.ClassNotFoundException;
}

La interfaz Externalizable extiende a Serializable añadiendo dos métodos, writeExternal() y readExternal() que son llamados automáticamente durante la serialización y la recuperación.

Si un objeto implementa a Externalizable no se serializa automáticamente nada y se debe especificar  lo que se debe serializar mediante llamadas a writeExternal().

SI un objeto implementa a Externalizable no se serializa automáticamente nada y se debe especificar lo que se debe serializar mediante llamadas a writeExternal().

Al contrario que en la serialización, al recuperar un objeto que ha sido externalizado se llama al constructor por defecto así que éste debe ser accesible.

Ejemplo

ARCHIVO USUARIO.JAVA

import java.io.*;
import java.util.*;

public class Usuario implements Externalizable{

     // Propiedades
     private String usuario;
     private String password;
    
     // Constructor
     public Usuario(){
          System.out.println("Creando usuario vacío");
     }
    
     Usuario(String u, String p){
          System.out.println("Creanado usuario (" + u + ", " + p + ")");
          usuario = u;
          password = p;
     }
    
     // Métodos
     public void writeExternal(ObjectOutput out) throws IOException{
          System.out.println("Usuario.WriteExternal");
          // Indicación explícita de los atributos a almacenar
          out.writeObject(usuario);
     }
    
     public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException{
          System.out.println("Usuario.readExternal");
          // Indicación explícita de los atributos a recuperar
          usuario = (String)in.readObject();
     }
    
     public void muestraUsuario(){
          String cad = "Usuario: " + usuario + " Password: ";
          if (password == null)
                cad = cad + "No disponible";
          else
                cad = cad + password;
          System.out.println(cad);
     }
}

ARCHIVO LISTAUSUARIOS.JAVA

import java.io.*;
import java.util.LinkedList;
import java.util.ListIterator;

public class ListaUsuarios implements Serializable{

     // Propiedades
     private LinkedList lista = new LinkedList();
     int valor;
    
     // Constructor
     ListaUsuarios(String[] usuarios, String[] passwords){
          for (int i = 0; i < usuarios.length; i++)
                lista.add(new Usuario(usuarios[i], passwords[i]));
     }
    
     // Métodos
     public void muestraUsuarios(){
          ListIterator li = lista.listIterator(0);
          Usuario u;
         
          while(li.hasNext()){
                u = (Usuario) li.next();
                u.muestraUsuario();
          }
     }
}

ARCHIVO DEMOEXTERNALIZABLE.JAVA

import java.io.*;

public class DemoExternalizable {

     public static void main(String[] args) throws IOException, ClassNotFoundException{
          System.out.println("Creando el objeto");
          String[] usuarios = {"Inazio", "Chuse", "Claver"};
          String[] passwords = {"1111", "2222", "3333"};
          ListaUsuarios lp = new ListaUsuarios(usuarios, passwords);
          System.out.println("Almacenando objeto");
          ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("D:\\objetos.out"));
          o.writeObject(lp);
          o.close();
         
          System.out.println("Recuperando objeto");
          ObjectInputStream in = new ObjectInputStream(new FileInputStream("D:\\objectos.out"));
          lp = (ListaUsuarios)in.readObject();
          lp.muestraUsuarios();

     }

}

Y la salida nos mostrará este resultado

Creando el objeto
Creanado usuario (Inazio, 1111)
Creanado usuario (Chuse, 2222)
Creanado usuario (Claver, 3333)
Almacenando objeto
Usuario.WriteExternal
Usuario.WriteExternal
Usuario.WriteExternal
Recuperando objeto
Creando usuario vacío
Usuario.readExternal
Creando usuario vacío
Usuario.readExternal
Creando usuario vacío
Usuario.readExternal
Usuario: Inazio Password: No disponible
Usuario: Chuse Password: No disponible
Usuario: Claver Password: No disponible

0 comentarios:

Publicar un comentario