Contenido de certificación
Buscar
Social
Ofertas laborales ES
« Seguimos con el paso de parámetros... ¿cómo resolverías el siguiente problema? | Main | Pregunta de examen: tipos primitivos y paso de valores »
miércoles
oct172012

Refactorización de un POJO

Puestos a plantear cuestiones que sugieran un debate, a la par que ayuden a mejorar el dominio del lenguaje, y la calidad del código, propongo un asunto bastante habitual.

Supongamos que tenemos una clase como la que sigue:

public class Lista<T> {

    private List<T> lista;

    public List<T> getLista() {

        return lista;

    }

    public void setLista(List<T> lista) {

        this.lista = lista;

    }

}

Con una refactorización del campo que es habitual en los IDE.

Se pretende refactorizar la clase, de manera que sea thread-safe, y que sea segura en todos los aspectos.

Reader Comments (12)

Quizá, ¿haciéndola inmutable?
public class Lista<T> {

private List<T> lista;

public List<T> getLista() {

return new List<T>(lista);

}

public void setLista(List<T> lista) {

this.lista = new List<T>(lista);

}

}

octubre 17, 2012 | Unregistered CommenterJavier

¡Sin quizás! ;)

Esta solución me parece mejor, porque elimina el setter, y permite declarar el campo como final:

public class Lista<T> {

private final List<T> lista;

public Lista(final List<T> lista) {
this.lista = lista == null ? new ArrayList<T>(0) : new ArrayList<T>(lista);
}

public List<T> getLista() {
return new ArrayList<T>(lista);
}
}

Ahora la cuestión es cómo operar con la lista, añadiendo, eliminando, verificando, siendo inmutable como es ;)

Nota: no se pueden crear instancias de List; supongo que querías poner ArrayList ;)

octubre 17, 2012 | Registered Commenterchoces

Me quedo loquer chavales.... Esnucau, lo tendre que pensar

octubre 17, 2012 | Registered Commenterjcarmonaloeches

Si se te quema alguna neurona pensando... ¡Reclama a James Gosling! ;D

octubre 17, 2012 | Registered Commenterchoces

Buenas, llevo tiempo siguiendo este portal, pero es la primera vez que aporto algo.

En realidad, poniendo la lista como final y recibiéndola en el constructor, lo que hace inmutable es la clase contenedora (Lista) no la lista en sí (List), a menos que el constructor reciba como argumento una lista inmutable. Si se quere que la lista no se pueda modificar, se debe asignar al atributo final, una copia inmutable:

public class Lista<T> {

private final List<T> lista;

public Lista(final List<T> lista) {
this.lista = lista == null ? Collections.emptyList() : Collections.unmodifiableList(lista);
}

public List<T> getLista() {
return lista;
}
}

Además, así nos evitaríamos tener que crear una copia cada vez que se invoca el getter.

Si la lista debe poderse modificar y no puede ser inmutable, podemos guardar una versión sincronizada (manteniendo inmutable la clase contenedora):

public class Lista<T> {

private final List<T> lista;

public Lista(final List<T> lista) {
this.lista = lista == null ? Collections.synchronizedList(new ArrayList<T>()) : Collections.synchronizedList(lista);
}

public List<T> getLista() {
return lista;
}
}

El objeto List que nos devuelve Collections.synchronizedList() es thread-safe (o se supone)

octubre 18, 2012 | Registered Commenteradeteran

@adeteran

¡Esperemos que haya más aportaciones tuyas, porque para ser la primera es excelente!
Has puesto sobre la mesa dos conceptos importantes, que sin duda usaremos: vista inmutable y concurrencia.

Tal y como lo implementas, la clase no tendría la flexibilidad que se pretende: o es inmutable o concurrente.
Lo que se plantea es que el campo lista se pueda modificar, al tiempo que permanece encapsulada, y que sea thread-safe.

public class Lista<T> {

private final List<T> lista;

public Lista(final List<T> lista) {
this.lista = lista == null ? new ArrayList<T>(0) : new ArrayList<T>(lista);
}

public List<T> getLista() {
return new ArrayList<T>(lista);
}

public List<T> getListaInmutable() {
return Collections.unmodifiableList(lista);
}
}

Con esta nueva versión, gracias a tu aportación, podemos devolver una vista inmutable, además de otra mutable. En ambos casos, lista permanece encapsulada.

Para resolver la cuestión de la multitarea, antes debemos implementar la mutabilidad encapsulada del campo lista :)

octubre 18, 2012 | Registered Commenterchoces

"El objeto List que nos devuelve Collections.synchronizedList() es thread-safe".

Sí, pero... al igual que con todas las clases de este estilo, como CopyOnWriteArrayList o ConcurrentHashMap, los iteradores no son thread-safe
Cuando se hace una iteración, debe incluirse en un bloque sincronizado.

octubre 18, 2012 | Registered Commenterchoces

interesantes respuesta y muy buen nivel técnico de debate.
gracias por las aportaciones, abro una nueva pregunta...
Me quedo con final es bueno para inmutable... no había caído... copón

octubre 18, 2012 | Registered Commenterjcarmonaloeches

Voy a proponer una solución:

public class Lista<T> {

private final List<T> lista;
private final ReentrantLock lock;

public Lista(final List<T> lista) {
this.lista = lista == null ? new ArrayList<T>(0) : new ArrayList<T>(lista);
this.lock = new ReentrantLock();
}

public List<T> getLista() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return new ArrayList<T>(lista);
} finally {
lock.unlock();
}
}

public List<T> getListaInmutable() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return Collections.unmodifiableList(lista);
} finally {
lock.unlock();
}
}

public boolean add(final T elemento) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return lista.add(elemento);
} finally {
lock.unlock();
}
}

public boolean remove(final T elemento) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return lista.remove(elemento);
} finally {
lock.unlock();
}
}

public boolean contains(final T elemento) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return lista.contains(elemento);
} finally {
lock.unlock();
}
}
}

octubre 18, 2012 | Registered Commenterchoces

No había caído en lo del iterador. Volviendo con las utilidades que nos proporciona la clase Collections, y simplificando la solución de choces, voy a proponer lo siguiente:

public class Lista<T> {

private final List<T> lista;

public Lista(final List<T> lista) {
this.lista = lista == null ? Collections.synchronizedList(new ArrayList<T>(0)) : Collections.synchronizedList(lista);
}

public List<T> getListaInmutable() {
return Collections.unmodifiableList(lista);
}

public boolean add(final T elemento) {
return lista.add(elemento);
}

public boolean remove(final T elemento) {
return lista.remove(elemento);
}

public boolean contains(final T elemento) {
return lista.contains(elemento);
}
}

Con la lista que nos da Collections.synchronizedList() no necesitamos sincronizar "a mano" los métodos add, remove y contains. Para iterar, usamos la lista inmutable.

Sólo veo un problema, y es que si un thread itera y otro modifica la lista, el que itera no ve los cambios. Pero eso ocurre con cualquier solución en el que se devuelva una copia, y no la lista interna de la clase.

Así el código queda más limpio, a menos que haya alguna ventaja con respecto a usar ReentrantLock en vez de la lista sincronizada que nos da Collections (que internamente usa el syncronized() de siempre).

octubre 18, 2012 | Registered Commenteradeteran

Ya sospechaba que vendrías con una solución de este tipo ;D
Por supuesto que es perfectamente válida; y en este caso tan sencillo, con una única lista, vale tanto como usar synchronized en los métodos, o usar una CopyOnWriteArrayList en vez de una ArrayList.

La razón por la que he propuesto una solución con ReentrantLock, aparte de para que se conozca (o se refresque la memoria) cómo se usa, es: si hay más de un campo del estilo de lista, por ejemplo más List, o Map, y no se desean usar (por el motivo que sea) las envolturas de Collections, o las clases específicamente sincronizadas, si se usan sincronizadores "globales" (sobre los métodos o sobre la misma clase como monitor), cuando una tarea accede a uno de los métodos, bloquea todos los demás, incluidos los de las colecciones que no le interesan.
Usando monitores específicos, como ReentrantLock, se puede atomizar mejor el acceso en esos casos: cada colección de la clase usa su propio lock.

octubre 18, 2012 | Registered Commenterchoces

El problema con los iteradores no creo que tenga una solución fácil, por lo que bien dices.
O bien se hace una sincronización externa (lo que puede ser una complicación), o bien se traslada el código de la iteración a esta misma clase, de manera que sea fácilmente sincronizable (aquí sí que habría que usar una sincronización explícita con monitores internos), y devolver el resultado.

octubre 18, 2012 | Registered Commenterchoces

PostPost a New Comment

Enter your information below to add a new comment.

My response is on my own website »
Author Email (optional):
Author URL (optional):
Post:
 
Some HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>