En ciertas situaciones una forma más prolija de serializar un objeto consiste en convertir su estado en una definicion XML y guardarlo o enviarlo donde haga falta. Esto nos permite poder ver el estado del objeto sobre el stream persistido incluso modificarlo con un editor de texto, cosa impensable sobre la salida de una serializacion estandar. Para tal fin podriamos complicarnos usando XStream o alguno de los tantos OXM (Object XML Mapper) que hay dando vueltas por la red. Calma! Antes de bajar los 20MB de librerias que nos pide el Apache Digester podemos echarle un vistazo a dos clases incluidas en J2SE: XMLEncoder y XMLDecoder.
Estas clases sirven exactamente para nuestros propositos: Transformar objetos en definiciones XML, y definiciones XML en objetos, sin perder nada en el medio. Sin configuraciones ni descriptores extraños estas dos clase son tan faciles de usar como un ObjectInputStream y objectOutputStream.
Objeto -> XML
Perro p = new Perro();
FileOutputStream f = new FileOutputStream("perro.xml");
XMLEncoder xe = new XMLEncoder(f);
xe.writeObject(p);
xe.close();
XML -> Objeto
FileInputStream f = new FileInputStream("perro.xml");
XMLDecoder xd = new XMLDecoder(f);
Perro p = (Perro)xd.readObject();
Esta demas decir que el XML contiene todo el grafo del objeto (el perro con sus pulgas, collar, dueño y cualquier otra dependencia) haciendose cargo de forma impecable de las referencias circulares.
Los drawbacks de este metodo si lo comparamos con la serializacion tradicional es el mismo que se le atribuye a RMI vs Webservices: El XML es mas lento y pesado que su par binario. A favor de este método podemos decir que el objeto serializado es un xml que puede editarse en cualquier lado y que se elimina el problema del cambio de version de las clases. Ya no hay que preocuparse por conflictos de versionado con el serialVersionUID porque la unica informacion que se guarda en el xml sobre la clase de las instancias es el nombre.
Cuanto tiempo sin escribir… en fin. Siempre me llamo la atención que en Java muchas de las operaciones sobre primitivos no son atómicas. El statement c++; por ejemplo no es atómico y puede traernos muchos dolores de cabeza en entornos de multithreading. Vale recordar, consideramos una instrucción como “atómica” si no puede ser interrumpida para darle paso a otro thread. En el siguiente ejemplo se presentan el caso de dos threads que consumen concurrentemente un contador. Si ejecutan el código podrán ver que no se obtiene el resultado esperado.
public class Main {
public static void main(String[] args) {
Contador c = new Contador();
new Corredor(c).start();
new Corredor(c).start();
}
}
class Corredor extends Thread {
private Contador contador;
@Override
public void run() {
for (int i = 0; i < 20; i++) {
try {
System.out.println(contador.incrementAndGet());
Thread.sleep(500);
} catch(Exception e) {
}
}
}
public Corredor(Contador contador) {
this.contador = contador;
}
}
class Contador {
private int value = 0;
public int incrementAndGet() {
return ++value;
}
}
En la pantalla terminamos con algo parecido a
1 2 2 3 3 4 4 5 6 6 7 7 8 8 9 9 10 10 11 ……
De alguna forma estamos forzando un poco esa situacion poniendo a dormir el Thread luego de llamar al metodo incrementAndGet() pero es una situacion que perfectamente se puede dar cuando hay muchos threads en ejecución. Podriamos corregir esta situación synchronizando el metodo incrementAndGet() pero produciría una sobrecarga que quizá no estemos dispuestos a pagar.
java.util.concurrent.atomic.AtomicInteger es la clase que vamos a usar para solucionar este problema. AtomicInteger se comporta como un Integer comun y corriente pero es mutable a traves de metodos atómicos. Lo mejor es que los metodos no son sincronizados de forma tradicional sino por otras oscuras tecnicas que en su momento trataré. Esta clase posee metodos tales como addAndGet(int delta) que incrementa en un delta de forma atomica, decrementAndGet(), getAndAdd(), getAndIncrement() y muchos otros que hacen más o menos lo que indican los nombres pero siempre siempre de forma atómica.
Existen muchas librerias que permiten manejar archivos XML dentro de Java. Tenemos desde Apache Commons Digester hasta StAX incorporado en la JRE6 pasando por incontables alternativas con menos fama. Si a estas alturas hay algun desprevenido que no sabe que es XML le recomiendo este articulo. Si bien hay muchas alternativas algunas son algo complicadas y otras presentan una sobreingenieria extrema para algo que parece tan trivial como el mapeo de un documento xml a un grafo de objetos.
XStream es una de esas librerias que hacen fáciles las cosas simples. Con XStream no hay que armar grandes descriptores para hacer funcionar el mapeo sino que todo apela a la vieja y querida API Reflection, además XStream mantiene al minimo las dependencias con otros proyectos (exactamente lo opuesto que ocurre con Digester que requiere casi todo el apache commons para funcionar). La forma de usar XStream es muy sencilla. Veremos como hacer para pasar de Objetos a XML y de XML a Objetos. Supongamos que tenemos una clase Persona y una Telefono.
public class Persona {
private String nombre;
private long id;
private String fechaNacimiento;
private float sueldo;
private Telefono[] telefonos = new Telefono[10];
//setters y getters.....
}
public class Telefono {
private String codigoPais;
private String codigoArea;
private String numero;
private String interno;
//getters y setters....
}
Más POJO imposible, ni siquiera hay que hacer a la clase Serializable o algo así. Para hacer mas claros estos ejemplos y no bombardear la pantalla con código inutil voy a apoyarme en una clase Generator a partir de la cual obtengo un objeto Persona o un String con la definicion XML. La definicion XML estará en un archivo, o en internet o en cualquier lado, como sea, la operatoria es la misma.
Ejemplo XML -> Objeto
import com.thoughtworks.xstream.XStream;
public class TestRead {
public static void main(String[] args) {
XStream xs = new XStream();
xs.alias("persona",Persona.class);
xs.alias("telefono",Telefono.class);
Persona p = (Persona)xs.fromXML(Generator.getXML());
//listo! en 3 lineas hace la conversion de XML a un Objeto de clase Persona.
System.out.println(p.getId());
System.out.println(p.getNombre());
System.out.println(p.getSueldo());
for(Telefono t:p.getTelefonos())
System.out.println(t.getNumero());
}
}
Ejemplo Objeto -> XML
public class TestWrite {
public static void main(String[] args) {
Persona p = Generator.getPersona();
XStream xs = new XStream();
xs.alias("persona", Persona.class);
xs.alias("telefono",Telefono.class);
String xml = xs.toXML(p);
// toXML() devuelve un string con la definicion xml para el objeto p. Todo en 3 lineas!
System.out.println(xml);
}
}
En el mundo de la OOP existe un tipo de Objeto denominado “Inmutable” que consiste en un objeto en el que su estado, una vez definido, no cambia durante todo su tiempo de vida.
Java incorpora unos cuantas clases cuyas instancias tienen la propiedad de ser inmutables. Las clases que envuelven los tipos primitivos (Byte, Short, Character, Integer, Long, Float, Double) son inmutables. La clase String es inmutable. ¿Por qué son inmutables?¿Para complicarnos la existencia? No realmente…
Asi como en el experimento del Gato de Schrödinger creamos una clase Gato con un atributo estado de tipo String. El estado del gato nos dice si el gato esta “VIVO” ó “MUERTO”
public class Gato {
private String estado;
public void setEstado(String e) {
this.estado = e;
}
public String getEstado() {
return estado;
}
}
vamos a suponer que la clase String tuviera un metodo setValue() que me permita cambiar la cadena de texto almacenada en una instancia particular de String. Es decir, supongamos que los objetos String no son inmutables y podemos cambiar el texto que contiene.
public class TestSchrödinger {
public static void main(String[] args) {
Gato gato = new Gato();
String e = "vivo";
gato.setEstado(e);
//El gato esta vivo
e.setValue("muerto");
//A nuestro String mutable le cambiamos el texto que encapsula.
System.out.println(e);
//Si esto es cierto entonces deberia salir impreso en la pantalla "muerto"
System.out.println(gato.getEstado());
//¿El gato esta vivo o esta muerto?
El problema de la mutabilidad consiste en que los parametros en Java siempre son pasados por valor, inclusos las referencias a objeto pero no el objeto al que apunta. Si un String, por ejemplo, podria cambiar el valor que contiene entonces aparecerian inconsistencia en el estado de los objetos que en algun momento incorporaron ese String a su estado.
Si no les quedo muy claro tal vez este video ayude un poco