Page 1

El modificador transient Cuando un miembro 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. La clase Cliente tiene dos miembros dato, el nombre del cliente y la contraseña o password. Redefine la función toString miembro de la clase base Object. Esta función devolverá el nombre del cliente y la contraseña. En el caso de que el miembro password guarde el valor null se imprimirá el texto (no disponible). En el cuadro que sigue se muestra el código que define la clase Cliente.

public class Cliente implements java.io.Serializable{ private String nombre; private transient String passWord; public Cliente(String nombre, String pw) { this.nombre=nombre; passWord=pw; } public String toString(){ String texto=(passWord==null) ? "(no disponible)" : passWord; texto+=nombre; return texto; } } En el cuadro siguiente se muestra los pasos para guardar un objeto de la clase Cliente en el archivo cliente.obj. Posterioremente, se lee el archivo para reconstruir el objeto obj1 de dicha clase. 1. Se crea el objeto cliente de la clase Cliente pasándole el nombre del cliente "Angel" y la contraseña "xyz". 2. Se crea un flujo de salida (objeto salida de la clase ObjectOutputStream) y se asocia con un objeto de la clase FileOutputStream para guardar la información en el archivo cliente.obj. 3. Se escribe el objeto cliente en el flujo de salida mediante writeObject. 4. Se cierra el flujo de salida llamando a close. Cliente cliente=new Cliente("Angel", "xyz"); ObjectOutputStream salida=new ObjectOutputStream(new FileOutputStream("cliente.obj")); salida.writeObject("Datos del cliente\n"); salida.writeObject(cliente); salida.close();


Para reconstruir el objeto obj1 de la clase Cliente se procede del siguiente modo: 1. Se crea un flujo de entrada (objeto entrada de la clase ObjectInputStream) y se asocia con un objeto de la clase FileInputStream para leer la información que gurada el archivo cliente.obj. 2. Se lee el objeto cliente en el flujo de salida mediante readObject. 3. Se imprime en la pantalla dicho objeto llamando implícitamente a su función miembro toString. 4. Se cierra el flujo de entrada llamando a close. ObjectInputStream entrada=new ObjectInputStream(new FileInputStream("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(); La salida del programa es Datos del cliente (no disponible) Angel Lo que nos indica que la información sensible guardada en el miembro dato password que tiene por modificador transient no ha sido guardada en el archivo. En la reconstrucción del objeto obj1 con la información guardada en el archivo el miembro dato password toma el valor null.

Objetos compuestos Volvemos de nuevo al estudio de la clase Rectangulo que contiene un subobjeto de la clase Punto. A dichas clases se les ha añadido la redefinición de la función toString miembro de la clase base Object (esta redefinición no es necesaria aunque es ilustrativa para explicar el comportamiento de un objeto compuesto). Como podemos apreciar, ambas clases implementan el interface Serializable. En el cuadro que sigue se muestra parte del código que define la clase Punto. public class Punto implements java.io.Serializable{ private int x; private int y; //otros miembros... public String toString(){ return new String("("+x+", "+y+")"); } } La definición de la clase Rectangulo se muestra en el siguiente cuadro public class Rectangulo implements java.io.Serializable{


private int ancho ; private int alto ; private Punto origen; //otras funciones miembro... public String toString(){ String texto=origen+" w:"+ancho+" h:"+alto; return texto; } } Como podemos observar, en la definición de toString de la clase Rectangulo se hace una llamada implícita a la función toString miembro de la clase Punto. La composición como se ha estudiado permite reutilizar el código existente. Para guardar en un archivo un objeto de la clase Rectangulo hay que seguir los mismos pasos que para guardar un objeto de la clase Lista o de la clase Cliente. Rectangulo rect=new Rectangulo(new Punto(10,10), 30, 60); ObjectOutputStream salida=new ObjectOutputStream(new FileOutputStream("figura.obj")); salida.writeObject("guardar un objeto compuesto\n"); salida.writeObject(rect); salida.close(); Para reconstruir un objeto de la clase Rectangulo a partir de los datos guardados en el archivo hay que seguir los mismos pasos que en los dos ejemplos previos. ObjectInputStream entrada=new ObjectInputStream(new FileInputStream("figura.obj")); String str=(String)entrada.readObject(); Rectangulo obj1=(Rectangulo)entrada.readObject(); System.out.println("------------------------------"); System.out.println(str+obj1); System.out.println("------------------------------"); entrada.close(); En el caso de que nos olvidemos de implementar el interface Serializable en la clase Punto que describe el subobjeto de la clase Rectangulo, se lanza una excepción, imprimiéndose en la consola. java.io.NotSerializableException: archivo5.Punto.

La herencia En el apartado anterior hemos examinado la composición, ahora examinemos la herencia. En el capítulo de la herencia examinamos una jerarquía formada por una clase base denominada Figura y dos clases derivadas denominadas Circulo y Rectangulo. Como podemos observar en el cuadro adjunto se han hecho dos modificaciones. La clase base Figura implementa el interface Serializable y en la clase Circulo en vez de usar el número PI proporcionado por la clase Math, definimos una constante estática PI con una aproximación de 4


decimales. De este modo probamos el comportamiento de un miembro estático en el proceso de serialización. Para serializar objetos de una jerarquía solamente la clase base tiene que implementar el interface Serializable public abstract class Figura implements java.io.Serializable{ protected int x; protected int y; public Figura(int x, int y) { this.x=x; this.y=y; } public abstract double area(); } class Circulo extends Figura{ protected double radio; private static final double PI=3.1416; public Circulo(int x, int y, double radio){ super(x,y); this.radio=radio; } public double area(){ return PI*radio*radio; } } class Rectangulo extends Figura{ protected double ancho, alto; public Rectangulo(int x, int y, double ancho, double alto){ super(x,y); this.ancho=ancho; this.alto=alto; } public double area(){ return ancho*alto; } } Vamos a serializar dos objetos uno de la clase Rectangulo y otro de la clase Circulo, y a continuación reconstruiremos dichos objetos. Una vez de que dispongamos de los objetos llamaremos a las funciones area para calcular el área de cada una de las figuras. Para guardar en el archivo figura.obj un objeto fig1 de la clase Rectangulo y otro objeto fig2 de la clase Circulo, se siguen los mismos pasos que hemos estudiado en apartados anteriores Figura fig1=new Rectangulo(10,15, 30, 60); Figura fig2=new Circulo(12,19, 60); ObjectOutputStream salida=new ObjectOutputStream(new FileOutputStream("figura.obj")); salida.writeObject("guardar un objeto de una clase derivada\n"); salida.writeObject(fig1); salida.writeObject(fig2);


salida.close(); Fijarse que fig1 y fig2 son dos referencias de la clase base Figura en la que se guardan objetos de las clases derivadas Rectangulo y Circulo, respectivamente Para leer los datos guardados en el archivo figura.obj y reconstruir dos objetos obj1 y obj2 de las clases Rectangulo y Circulo respectivamente, se procede de forma similar a la estudiada en los apartados previos. ObjectInputStream entrada=new ObjectInputStream(new FileInputStream("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(); Fijarse que obj1 y obj2 son referencias a la clase base Figura. Sin embargo, cuando obj1 llama a la función area nos devuelve (correctamente) el área del rectángulo y cuando, obj2 llama a la función area devuelve el área del círculo. Fijarse también que aunque PI es un miembro estático de la clase Circulo, se reconstruye el objeto obj2 con el valor del miembro estático con el que se calcula el área del círculo.

El modificador transient