You are on page 1of 12

Hilos en Java

A veces necesitamos que nuestro programa Java realice varias cosas simultáneamente. Otras
veces tiene que realizar una tarea muy pesada, por ejemplo, consultar en el listín telefónico
todos los nombres de chica que tengan la letra n, que tarda mucho y no deseamos que todo se
quede parado mientras se realiza dicha tarea. Para conseguir que Java haga varias cosas a la
vez o que el programa no se quede parado mientras realiza una tarea compleja, tenemos los
hilos (Threads).
Crear un Hilo
Crear un hilo en java es una tarea muy sencilla. Basta heredar de la clase Thread y definir el
método run(). Luego se instancia esta clase y se llama al método start() para que arranque
el hilo. Más o menos esto
public MiHilo extends Thread
{
public void run()
{
// Aquí el código pesado que tarda mucho
}
};
...
MiHilo elHilo = new MiHilo();
elHilo.start();
System.out.println("Yo sigo a lo mio");
Listo. Hemos creado una clase MiHilo que hereda de Thread y con un método run(). En el
método run() pondremos el código que queremos que se ejecute en un hilo separado. Luego
instanciamos el hilo con un new MiHilo() y lo arrancamos con elHilo.start(). El System.out
que hay detrás se ejecutará inmediatamente después del start(), haya terminado o no el
código del hilo.
Detener un hilo
Suele ser una costumbre bastante habitual que dentro del método run() haya un bucle
infinito, de forma que el método run() no termina nunca. Por ejemplo, supongamos un chat.
Cuando estás chateando, el programa que tienes entre tus manos está haciendo dos cosas
simultáneamente. Por un lado, lee el teclado para enviar al servidor del chat todo lo que tú
escribas. Por otro lado, está leyendo lo que llega del servidor del chat para escribirlo en tu
pantalla. Una forma de hacer esto es "por turnos"
while (true)
{
leeTeclado();
enviaLoLeidoAlServidor();
leeDelServidor();
muestraEnPantallaLoLeidoDelServidor();
}
Esta, desde luego, es una opción, pero sería bastante "cutre", tendríamos que hablar por
turnos. Yo escribo algo, se le envía al servidor, el servidor me envía algo, se pinta en pantalla
y me toca a mí otra vez. Si no escribo nada, tampoco recibo nada. Quizás sea buena opción
para los que no son ágiles leyendo y escribiendo, pero no creo que le guste este mecanismo a
la mayoría de la gente.
Lo normal es hacer dos hilos, ambos en un bucle infinito, leyendo (del teclado o del servidor) y
escribiendo (al servidor o a la pantalla). Por ejemplo, el del teclado puede ser así
public void run()
{
while (true)
{
String texto = leeDelTeclado();
enviaAlServidor(texto);
}
}
Esta opción es mejor, dos hilos con dos bucles infinitos, uno encargado del servidor y otro del
teclado.
Ahora viene la pregunta del millón. Si queremos detener este hilo, ¿qué hacemos?. Los
Thread de java tienen muchos métodos para parar un hilo: detroy(), stop(), suspend() ...
Pero, si nos paramos a mirar la API de Thread, nos llevaremos un chasco. Todos esos
métodos son inseguros, están obsoletos, desaconsejados o las tres cosas juntas.
¿Cómo paramos entonces el hilo?
La mejor forma de hacerlo es implementar nosotros mismos un mecanismo de parar, que lo
único que tiene que hacer es terminar el método run(), saliendo del bucle.
Un posible mecanismo es el siguiente
public class MiHilo extends Thread
{
// boolean que pondremos a false cuando queramos parar el hilo
private boolean continuar = true;

// metodo para poner el boolean a false.


public void detenElHilo()
{
continuar=false;
}

// Metodo del hilo


public void run()
{
// mientras continuar ...
while (continuar)
{
String texto = leeDelTeclado();
enviaAlServidor(texto);
}
}
}
Simplemente hemos puesto en la clase un boolean para indicar si debemos seguir o no con el
bucle infinito. Por defecto a true. Luego hemos añadido un método para cambiar el valor de
ese boolean a false. Finalmente hemos cambiado la condición del bucle que antes era true y
ahora es continuar.
Para parar este hilo, es sencillo
MiHilo elHilo = new MiHilo();
elHilo.start();
// Ya tenemos el hilo arrancado
...
// Ahora vamos a detenerlo
elHilo.detenElHilo();
Seguimos ahora sincronizando hilos...

Sincronización de hilos
... viene de hilos en java.
Cuando en un programa tenemos varios hilos corriendo simultáneamente es posible que varios
hilos intenten acceder a la vez a un mismo sitio (un fichero, una conexión, un array de datos)
y es posbible que la operación de uno de ellos entorpezca la del otro. Para evitar estos
problemas, hay que sincronizar los hilos. Por ejemplo, si un hilo con vocación de Cervantes
escribe en fichero "El Quijote" y el otro con vocación de Shakespeare escribe "Hamlet", al final
quedarán todas las letras entremezcladas. Hay que conseguir que uno escriba primero su
Quijote y el otro, luego, su Hamlet.
Sincronizar usando un objeto
Imagina que escribimos en un fichero usando una variable fichero de tipo PrintWriter. Para
escribir uno de los hilos hará esto
fichero.println("En un lugar de la Mancha...");
Mientras que el otro hará esto
fichero.println("... ser o no ser ...");
Si los dos hilos lo hacen a la vez, sin ningún tipo de sincronización, el fichero al final puede
tener esto
En un ... ser lugar de la o no Mancha ser ...
Para evitarlo debemos sincronizar los hilos. Cuando un hilo escribe en el fichero, debe marcar
de alguna manera que el fichero está ocupado. El otro hilo, al intentar escribir, lo verá ocupado
y deberá esperar a que esté libre. En java esto se hace fácilmente. El código sería así
synchronized (fichero)
{
fichero.println("En un lugar de la Mancha...");
}
y el otro hilo
synchronized (fichero)
{
fichero.println("... ser o no ser ...");
}
Al poner synchronized(fichero) marcamos fichero como ocupado desde que se abren las
llaves de después hasta que se cierran. Cuando el segundo hilo intenta también su
synchronized(fichero), se queda ahí bloqueado, en espera que de que el primero termine
con fichero. Es decir, nuestro hilo Shakespeare se queda parado esperando en el
synchronized(fichero) hasta que nuestro hilo Cervantes termine.
synchronized comprueba si fichero está o no ocupado. Si está ocupado, se queda esperando
hasta que esté libre. Si está libre o una vez que esté libre, lo marca como ocupado y sigue el
código.
Este mecanismo requiere colaboración entre los hilos. El que hace el código debe acordarse
de poner synchronized siempre que vaya a usar fichero. Si no lo hace, el mecanismo no sirve
de nada.
Métodos sincronizados
Otro mecanismo que ofrece java para sincronizar hilos es usar métodos sincronizados. Este
mecanismo evita además que el que hace el código tenga que acordarse de poner
synchronized.
Imagina que encapsulamos fichero dentro de una clase y que ponemos un método
synchronized para escribir, tal que así
public class ControladorDelFichero
{
private PrintWriter fichero;

public ControladorFichero()
{
// Aqui abrimos el fichero y lo dejamos listo
// para escribir.
}
public synchronized void println(String cadena)
{
fichero.println(cadena);
}
}
Una vez hecho esto, nuestros hilos Cervantes y Shakespeare sólo tienen que hacer esto
ControladorFichero control = new ControladorFichero();
...
// Hilo Cervantes
control.println("En un lugar de la Mancha ...");
...
// Hilo Shakespeare
control.println("... ser o no ser ...");
Al ser el método println() synchronized, si algún hilo está dentro de él ejecutando el código,
cualquier otro hilo que llame a ese método se quedará bloqueado en espera de que el primero
termine.
Este mecanismo es más mejor porque, siguiendo la filosfía de la orientación a objetos,
encapsula más las cosas. El fichero requiere sincronización, pero ese conocimiento sólo lo
tiene la clase ControladorFichero. Los hilos Cervantes y Shakespeare no saben nada del
tema y simplemente se ocupan de escribir cuando les viene bien. Tampoco depende de la
buena memoria del programador a la hora de poner el synchronized(fichero) de antes.
Otros objetos que necesitan sincronización
Hemos puesto de ejemplo un fichero, pero requieren sincronización en general cualquier
entrada y salida de datos, como pueden ser ficheros, sockets o incluso conexiones con bases
de datos.
También pueden necesitar sincronización almacenes de datos en memoria, como LinkedList,
ArrayList, etc. Imagina, por ejemplo, en una LinkedList que un hilo está intentando sacar
por pantalla todos los datos
LinkedList lista = new LinkedList();
...
for (int i=0;i<lista.size(); i++)
System.out.println(lista.get(i));
Estupendo y maravilloso pero ... ¿qué pasa si mientras se escriben estos datos otro hilo borra
uno de los elementos?. Imagina que lista.size() nos ha devuelto 3 y justo antes de intentar
escribir el elemento 2 (el último) viene otro hilo y borra cualquiera de los elementos de la lista.
Cuando intentemos el lista.get(2) nos saltará una excepción porque la lista ya no tiene tantos
elementos.
La solución es sincronizar la lista mientras la estamos usando
LinkedList lista = new LinkedList();
...
synchronized (lista)
{
for (int i=0;i<lista.size(); i++)
System.out.println(lista.get(i));
}
además, este tipo de sincronización es la que se requiere para mantener "ocupado" el objeto
lista mientras hacemos varias llamadas a sus métodos (size() y get()), no queda más
remedio que hacerla así. Por supuesto, el que borra también debe preocuparse del
synchronized.

Esperando datos: wait() y notify()


... viene de sincronizar hilos.
A veces nos interesa que un hilo se quede bloqueado a la espera de que ocurra algún evento,
como la llegada de un dato para tratar o que el usuario termine de escribir algo en una
interface de usuario. Todos los objetos java tienen el método wait() que deja bloqueado al
hilo que lo llama y el método notify(), que desbloquea a los hilos bloqueados por wait().
Vamos a ver cómo usarlo en un modelo productor/consumidor.
Bloquear un hilo
Antes de nada, que quede claro que las llamdas a wait() lanzan excepciones que hay que
capturar. Todas las llamadas que pongamos aquí debería estar en un bloque try-catch, así
try
{
// llamada a wait()
}
catch (Exception e)
{
....
}
pero para no liar mucho el código y mucho más importante, no auto darme más trabajo de
escribir de la cuenta, no voy a poner todo esto cada vez. Cuando hagas código, habrá que
ponerlo.
Vamos ahora a lo que vamos...
Para que un hilo se bloquee basta con que llame al método wait() de cualquier objeto. Sin
embargo, es necesario que dicho hilo haya marcado ese objeto como ocupado por medio de
un synchronized. Si no se hace así, saltará una excepción de que "el hilo no es propietario
del monitor" o algo así.
Imaginemos que nuestro hilo quiere retirar datos de una lista y si no hay datos, quiere
esperar a que los haya. El hilo puede hacer algo como esto
synchronized(lista);
{
if (lista.size()==0)
lista.wait();

dato = lista.get(0);
lista.remove(0);
}
En primer lugar hemos hecho el synchronized(lista) para "apropiarnos" del objeto lista.
Luego, si no hay datos, hacemos el lista.wait(). Una vez que nos metemos en el wait(), el
objeto lista queda marcado como "desocupado", de forma que otros hilos pueden usarlo.
Cuando despertemos y salgamos del wait(), volverá a marcarse como "ocupado."
Nuestro hilo se desbloquerá y saldrá del wait() cuando alguien llame a lista.notify(). Si el
hilo que mete datos en la lista llama luego a lista.notify(), cuando salgamos del wait()
tendremos datos disponibles en la lista, así que únicamente tenemos que leerlos (y borrarlos
para no volver a tratarlos la siguiente vez). Existe otra posibilidad de que el hilo se salga del
wait() sin que haya datos disponibles, pero la veremos más adelante.
Notificar a los hilos que están en espera
Hemos dicho que el hilo que mete datos en la lista tiene que llamar a lista.notify(). Para esto
también es necesario apropiarnos del objeto lista con un synchronized. El código del hilo que
mete datos en la lista quedará así
synchronized(lista)
{
lista.add(dato);
lista.notify();
}
Listo, una vez que hagamos esto, el hilo que estaba bloqueado en el wait() despertará, saldrá
del wait() y seguirá su código leyendo el primer dato de la lista.
wait() y notify() como cola de espera
wait() y notify() funcionan como una lista de espera. Si varios hilos van llamando a wait()
quedan bloqueados y en una lista de espera, de forma que el primero que llamó a wait() es el
primero de la lista y el último es el útlimo.
Cada llamada a notify() despierta al primer hilo en la lista de espera, pero no al resto, que
siguen dormidos. Necesitamos por tanto hacer tantos notify() como hilos hayan hecho wait()
para ir despertándolos a todos de uno en uno.
Si hacemos varios notify() antes de que haya hilos en espera, quedan marcados todos esos
notify(), de forma que los siguientes hilos que hagan wait() no se quedaran bloqueados.
En resumen, wait() y notify() funcionan como un contador. Cada wait() mira el contador y
si es cero o menos se queda bloqueado. Cuando se desbloquea decrementa el contador. Cada
notify() incrementa el contador y si se hace 0 o positivo, despierta al primer hilo de la cola.
Un símil para entenderlo mejor. Una mesa en la que hay gente que pone caramelos y gente
que los recoge. La gente son los hilos. Los que van a coger caramelos (hacen wait()) se
ponen en una cola delante de la mesa, cogen un caramelo y se van. Si no hay caramelos,
esperan que los haya y forman una cola. Otras personas ponen un caramelo en la mesa (hacen
notify()). El número de caramelos en la mesa es el contador que mencionabamos.
Modelo Productor/Consumidor
Nuevamente y como comentamos en sincronizar hilos, es buena costumbre de orientación a
objetos "ocultar" el tema de la sincronización a los hilos, de forma que no dependamos de que
el programador se acuerde de implemetar su hilo correctamente (llamada a synchronized y
llamada a wait() y notify()).
Para ello, es práctica habitual meter la lista de datos dentro de una clase y poner dos métodos
synchronized para añadir y recoger datos, con el wait() y el notify() dentro.
El código para esta clase que hace todo esto puede ser así
public class MiListaSincronizada
{
private LinkedList lista = new LinkedList();
public synchronized void addDato(Object dato)
{
lista.add(dato);
lista.notify();
}

public synchronized Object getDato()


{
if (lista.size()==0)
wait();
Object dato = lista.get(0);
lista.remove(0);
return dato;
}
}
Listo, nuestros hilos ya no deben preocuparse de nada. El hilo que espera por los datos hace
esto
Object dato = listaSincronizada.getDato();
y eso se quedará bloqueado hasta que haya algún dato disponible. Mientras, el hilo que guarda
datos sólo tiene que hacer esto otro
listaSincronizada.addDato(dato);
Interrumpir un hilo
Comentamos antes que es posible que un hilo salga del wait() sin necesidad de que nadie
haga notify(). Esta situación se da cuando se produce algún tipo de interrupción. En el caso
de java es fácil provocar una interrupción llamando al método interrupt() del hilo.
Por ejemplo, si el hiloLector está bloqueado en un wait() esperando un dato, podemos
interrumpirle con
hiloLector.interrupt();
El hiloLector saldrá del wait() y se encontrará con que no hay datos en la lista. Sabrá que
alguien le ha interrumpido y hará lo que tenga que hacer en ese caso.
Por ejemplo, imagina que tenemos un hilo lectorSocket pendiente de un socket (una
conexión con otro programa en otro ordenador a través de red) que lee datos que llegan del
otro programa y los mete en la listaSincronizada.
Imagina ahora un hilo lectorDatos que está leyendo esos datos de la listaSincronizada y
tratándolos.
¿Qué pasa si el socket se cierra?. Imagina que nuestro programa decide cerrar la conexión
(socket) con el otro programa en red porque se han enfadado y ya no piensan hablarse nunca
más. Una vez cerrada la conexión, el hilo lectorSocket puede interrumpir al hilo lectorDatos.
Este, al ver que ha salido del wait() y que no hay datos disponibles, puede suponer que se ha
cerrado la conexión y terminar.
El código del hilo lectorDatos puede ser así
while (true)
{
if (listaSincronizada.size() == 0)
wait();

// Debemos comprobar que efectivamente hay datos.


if (listaSincronizada.size() > 0)
{
// Hay datos, los tratamos
Object dato=listaSincronizada.get(0);
listaSincronizada.remove(0);
// tratar el dato.
}
else
{
// No hay, datos se debe haber cerrado la conexion
// así que nos salimos.
return;
}
}
y el hilo lectorSocket, cuando cierra la conexión, debe hacer
socket.close();
lectorDatos.interrupt();
Listo por ahora.

Comunicación entre Hilos


Anterior | Siguiente
Otra clave para el éxito y la ventaja de la utilización de múltiples hilos de ejecución en una aplicación,
o aplicación multithreaded, es que pueden comunicarse entre sí. Se pueden diseñar hilos para utilizar
objetos comunes, que cada hilo puede manipular independientemente de los otros hilos de ejecución.
El ejemplo clásico de comunicación de hilos de ejecución es un modelo productor/consumidor. Un hilo
produce una salida, que otro hilo usa (consume), sea lo que sea esa salida. Entonces se crea un
productor, que será un hilo que irá sacando caracteres por su salida; y se crea también un
consumidor que irá recogiendo los caracteres que vaya sacando el productor y un monitor que
controlará el proceso de sincronización entre los hilos de ejecución. Funcionará como una tubería,
insertando el productor caracteres en un extremo y leyéndolos el consumidor en el otro, con el
monitor siendo la propia tubería.

Productor
El productor extenderá la clase Thread, y su código es el siguiente:
class Productor extends Thread {
private Tuberia tuberia;
private String alfabeto = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

public Productor( Tuberia t ) {


// Mantiene una copia propia del objeto compartido
tuberia = t;
}

public void run() {


char c;

// Mete 10 letras en la tubería


for( int i=0; i < 10; i++ )
{
c = alfabeto.charAt( (int)(Math.random()*26 ) );
tuberia.lanzar( c );
// Imprime un registro con lo añadido
System.out.println( "Lanzado "+c+" a la tuberia." );
// Espera un poco antes de añadir más letras
try {
sleep( (int)(Math.random() * 100 ) );
} catch( InterruptedException e ) {;}
}
}
}
Notar que se crea una instancia de la clase Tuberia, y que se utiliza el método tuberia.lanzar() para
que se vaya construyendo la tubería, en principio de 10 caracteres.
Consumidor
Ahora se reproduce el código del consumidor, que también extenderá la clase Thread:
class Consumidor extends Thread {
private Tuberia tuberia;

public Consumidor( Tuberia t ) {


// Mantiene una copia propia del objeto compartido
tuberia = t;
}

public void run() {


char c;

// Consume 10 letras de la tubería


for( int i=0; i < 10; i++ )
{
c = tuberia.recoger();
// Imprime las letras retiradas
System.out.println( "Recogido el caracter "+c );
// Espera un poco antes de coger más letras
try {
sleep( (int)(Math.random() * 2000 ) );
} catch( InterruptedException e ) {;}
}
}
}
En este caso, como en el del productor, se cuenta con un método en la clase Tuberia,
tuberia.recoger(), para manejar la información.
Monitor
Una vez vistos el productor de la información y el consumidor, solamente queda por ver qué es lo que
hace la clase Tuberia.
Lo que realiza la clase Tuberia, es una función de supervisión de las transacciones entre los dos hilos
de ejecución, el productor y el consumidor. Los monitores, en general, son piezas muy importantes
de las aplicaciones multihilo, porque mantienen el flujo de comunicación entre los hilos.
class Tuberia {
private char buffer[] = new char[6];
private int siguiente = 0;
// Flags para saber el estado del buffer
private boolean estaLlena = false;
private boolean estaVacia = true;

// Método para retirar letras del buffer


public synchronized char recoger() {
// No se puede consumir si el buffer está vacío
while( estaVacia == true )
{
try {
wait(); // Se sale cuando estaVacia cambia a false
} catch( InterruptedException e ) {
;
}
}
// Decrementa la cuenta, ya que va a consumir una letra
siguiente--;
// Comprueba si se retiró la última letra
if( siguiente == 0 )
estaVacia = true;
// El buffer no puede estar lleno, porque acabamos
// de consumir
estaLlena = false;
notify();

// Devuelve la letra al thread consumidor


return( buffer[siguiente] );
}

// Método para añadir letras al buffer


public synchronized void lanzar( char c ) {
// Espera hasta que haya sitio para otra letra
while( estaLlena == true )
{
try {
wait(); // Se sale cuando estaLlena cambia a false
} catch( InterruptedException e ) {
;
}
}
// Añade una letra en el primer lugar disponible
buffer[siguiente] = c;
// Cambia al siguiente lugar disponible
siguiente++;
// Comprueba si el buffer está lleno
if( siguiente == 6 )
estaLlena = true;
estaVacia = false;
notify();
}
}
En la clase Tuberia se pueden observar dos características importantes: los miembros dato (buffer[])
son privados, y los métodos de acceso (lanzar() y recoger()) son sincronizados.
Aquí se observa que la variable estaVacia es un semáforo, como los de toda la vida. La naturaleza
privada de los datos evita que el productor y el consumidor accedan directamente a éstos. Si se
permitiese el acceso directo de ambos hilos de ejecución a los datos, se podrían producir problemas;
por ejemplo, si el consumidor intenta retirar datos de un buffer vacío, obtendrá excepciones
innecesarias, o se bloqueará el proceso.
Los métodos sincronizados de acceso impiden que los productores y consumidores corrompan un
objeto compartido. Mientras el productor está añadiendo una letra a la tubería, el consumidor no la
puede retirar y viceversa. Esta sincronización es vital para mantener la integridad de cualquier objeto
compartido. No sería lo mismo sincronizar la clase en vez de los métodos, porque esto significaría que
nadie puede acceder a las variables de la clase en paralelo, mientras que al sincronizar los métodos,
sí pueden acceder a todas las variables que están fuera de los métodos que pertenecen a la clase.
Se pueden sincronizar incluso variables, para realizar alguna acción determinada sobre ellas, por
ejemplo:
sincronized( p ) {
// aquí se colocaría el código
// los threads que estén intentando acceder a p se pararán
// y generarán una InterruptedException
}
El método notify() al final de cada método de acceso avisa a cualquier proceso que esté esperando
por el objeto, entonces el proceso que ha estado esperando intentará acceder de nuevo al objeto. En
el método wait() se hace que el hilo se quede a la espera de que le llegue un notify(), ya sea enviado
por el hilo de ejecución o por el sistema.
Ahora que ya se dispone de un productor, un consumidor y un objeto compartido, se necesita una
aplicación que arranque los hilos y que consiga que todos hablen con el mismo objeto que están
compartiendo. Esto es lo que hace el siguiente trozo de código, del fuente java1007.java:
class java1007 {
public static void main( String args[] ) {
Tuberia t = new Tuberia();
Productor p = new Productor( t );
Consumidor c = new Consumidor( t );

p.start();
c.start();
}
}
Compilando y ejecutando esta aplicación, se podrá observar en modelo que se ha diseñado en pleno
funcionamiento.
Monitorización del Productor
Los programas productor/consumidor a menudo emplean monitorización remota, que permite al
consumidor observar el hilo del productor interaccionando con un usuario o con otra parte del
sistema. Por ejemplo, en una red, un grupo de hilos de ejecución productores podrían trabajar cada
uno en una workstation. Los productores imprimirían documentos, almacenando una entrada en un
registro (log). Un consumidor (o múltiples consumidores) podría procesar el registro y realizar
durante la noche un informe de la actividad de impresión del día anterior.
Otro ejemplo, a pequeña escala podría ser el uso de varias ventanas en una workstation. Una
ventana se puede usar para la entrada de información (el productor), y otra ventana reaccionaría a
esa información (el consumidor).
Peer, es un observador general del sistema.

Serialización de objetos en java


De ChuWiki
Saltar a navegación, búsqueda
Tabla de contenidos
[esconder]
1 Serialización de un objeto: Implementar Serializable
2 Serialización a medida
3 Convertir un Serializable a byte[ ] y viceversa
4 Serial Version UID

Serialización de un objeto: Implementar Serializable


Para que un programa java pueda convertir un objeto en un montón de bytes y pueda luego
recuperarlo, el objeto necesita ser Serializable. Al poder convertir el objeto a bytes, ese
objeto se puede enviar a través de red, guardarlo en un fichero, y después recontruirlo al otra
lado de la red, leerlo del fichero,....
Para que un objeto sea serializable basta con que implemente la interface Serializable. Como
la interface Serializable no tiene métodos, es muy sencillo implementarla, basta con un
implements Serializable y nada más. Por ejemplo, la clase Datos siguiente es Serializable y
java sabe perfectamente enviarla o recibirla por red, a través de socket o de rmi. También
java sabe escribirla en un fichero o reconstruirla a partir del fichero.
public class Datos implements Serializable
{
public int a;
public String b;
public char c;
}
Si dentro de la clase hay atributos que son otras clases, estos a su vez también deben ser
Serializable. Con los tipos de java (String, Integer, etc) no hay problema porque lo son. Si
ponemos como atributos nuestras propias clases, estas a su vez deben implementar
Serializable. Por ejemplo
/* Esta clase es Serializable porque implementa Serializable y todos sus
* campos son Serializable, incluido "Datos f;"
*/
public class DatoGordo implements Serializable
{
public int d;
public Integer e;
Datos f;
}
Serialización a medida
En realidad, se llama "serializar un objeto" al proceso de convertirlo a bytes, para poder
enviarlo por una red, y reconstruirlo luego a partir de esos bytes.
A veces podemos necesitar que se haga algo especial en el momento de serializarlo, bien al
construirlo a bytes, bien al recibirlo. Por ello java nos permite hacerlo. Basta con hacer que
nuestro objeto defina uno o los dos métodos siguientes:
private void readObject(java.io.ObjectInputStream stream)
throws IOException, ClassNotFoundException
{
// Aqui debemos leer los bytes de stream y reconstruir el objeto
}

private void writeObject(java.io.ObjectOutputStream stream)


throws IOException
{
// Aquí escribimos en stream los bytes que queramos que se envien por red.
}
java llamará a estos métodos cuando necesite convertirlo a bytes para enviarlo por red o
cuando necesite reconstruirlo a partir de los bytes en red.

Convertir un Serializable a byte[ ] y viceversa


Podemos convertir cualquier objeto Serializable a un array de byte y viceversa.
Normalmento esto no es necesario para enviar el objeto por un socket o escribirlo en un
fichero puesto que contamos con las clases ObjectInputStream y ObjectOutputStream. Sin
embargo, en ocasiones, por ejemplo, al intentar enviar un objeto por un socket udp, sí es
necesario hacerlo.
Para hacer esta conversión, podemos usar este código
De objeto a byte[]
ByteArrayOutputStream bs= new ByteArrayOutputStream();
ObjectOutputStream os = new ObjectOutputStream (bs);
os.writeObject(unObjetoSerializable); // this es de tipo DatoUdp
os.close();
byte[] bytes = bs.toByteArray(); // devuelve byte[]
De byte[] a objeto
ByteArrayInputStream bs= new ByteArrayInputStream(bytes); // bytes es el byte[]
ObjectInputStream is = new ObjectInputStream(bs);
ClaseSerializable unObjetoSerializable = (ClaseSerializable)is.readObject();
is.close();
Una utilidad de estos dos códigos es el realizar copias "profundas" de un objeto, es decir, se
obtiene copia del objeto, copias de los atributos y copias de los atributos de los atributos.
Basta convertir el objeto a byte[] y luego reconstruirlo en otra variable.

Serial Version UID


Cuando pasamos objetos Serializable de un lado a otro tenemos un pequeño problema. Si la
clase que queremos pasar es objeto_serializable, lo normal, salvo que usemos Carga dinámica
de clases, es que en ambos lados (el que envía y el que recibe la clase), tengan su propia
copia del fichero objeto_serializable.class. Es posible que en distintas versiones de nuestro
programa la clase objeto_serializable cambie, de forma que es posible que un lado tenga una
versión más antigua que en el otro lado. Si sucede esto, la reconstrucción de la clase en el
lado que recibe es imposible.
Para evitar este problema, se aconseja que la clase objeto_serializable tenga un atributo
privado de esta forma
private static final long serialVersionUID = 8799656478674716638L;
de forma que el numerito que ponemos al final debe ser distinto para cada versión de
compilado que tengamos.
De esta forma, java es capaz de detectar rápidamente que las versiones de
objeto_serializable.class en ambos lados son distintas.
Algunos entornos de desarrollo, como eclipse, dan un warning si una clase que implementa
Serializable (o de una clase que a su vez implementa Serializable no tiene definido este
campo. Es más, puede generarlo automáticamente, número incluido, si se lo pedimos. En
eclipse basta con hacer click con el ratón sobre el símbolo de warning para que nos de las
posibles soluciones al warning. Una de ellas genera el número automáticamente.
Obtenido de "http://www.chuidiang.com/chuwiki/index.php?title=Serializaci
%C3%B3n_de_objetos_en_java"

You might also like