2009-06-11 29 views
5

Implementé una clase en Java que, internamente, almacena una lista . Quiero que la clase sea inmutable. Sin embargo, necesito realizar operaciones en los datos internos que no tienen sentido en el contexto de la clase. Por lo tanto, tengo otra clase que define un conjunto de algoritmos. Aquí está un ejemplo simplificado:Objetos inmutables en Java y acceso a datos

Wrapper.java

import java.util.List; 
import java.util.Iterator; 

public class Wrapper implements Iterable<Double> 
{ 
    private final List<Double> list; 

    public Wrapper(List<Double> list) 
    { 
     this.list = list; 
    } 

    public Iterator<Double> iterator() 
    { 
     return getList().iterator(); 
    } 

    public List<Double> data() { return getList(); } 
} 

Algorithm.java

import java.util.Iterator; 
import java.util.Collection; 

public class Algorithm 
{ 
    public static double sum(Collection<Double> collection) 
    { 
     double sum = 0.0; 
     Iterator<Double> iterator = collection.iterator(); 

     // Throws NoSuchElementException if the Collection contains no elements 
     do 
     { 
      sum += iterator.next(); 
     } 
     while(iterator.hasNext()); 

     return sum; 
    } 
} 

Ahora, mi pregunta es, ¿hay una forma segura de evitar que alguien modificando mis datos internos a pesar de que mi clase debe ser inmutable? Si bien proporcioné un método () con fines de solo lectura, no hay nada que impida que alguien modifique los datos mediante métodos como borrar() y quitar(). Ahora, me doy cuenta de que podría proporcionar acceso a mis datos exclusivamente a través de iteradores. Sin embargo, me han dicho que es típico pasar una Colección . En segundo lugar, si tuviera un algoritmo que requiriera más de una pasada sobre los datos, tendría que proporcionar múltiples iteradores, lo que parece una pendiente resbaladiza.

De acuerdo, con suerte hay una solución simple que resolverá mis preocupaciones. Acabo de volver a Java y nunca consideré estas cosas antes de tratar con const en C++. ¡Gracias por adelantado!

¡Oh! Hay una cosa más en la que solo pensé. Prácticamente no puedo devolver una copia de la lista interna. La lista generalmente contiene cientos de miles de elementos.

Respuesta

23

Puede usar Collections.unmodifiableList y modificar el método de datos.

public List<Double> data() { return Collections.unmodifiableList(getList()); } 

Desde el javadoc:

Devuelve una vista no se puede modificar de la lista especificada . Este método permite a los módulos proporcionar a los usuarios el acceso de "solo lectura" a las listas internas. operaciones de consulta en la lista devuelta "leen a través" de la lista especificada, y los intentos de modificar el lista devuelta, ya sea directa oa través de su iterador, da como resultado un UnsupportedOperationException.

+0

¡Perfecto! No vi esto. Gracias. –

+1

Aún tiene que ocuparse de la forma en que se crea el objeto Wrapper: dado que la Lista se pasa al constructor, puede haber referencias a ella en otro lugar del código. Si desea que Wrapper sea realmente inmutable, tendrá que copiar el contenido de la lista. –

5

Java no tiene un concepto sintáctico de clase inmutable. Depende de usted, como programador, dar acceso a las operaciones, pero siempre debe suponer que alguien abusará de ellas.

Un objeto verdaderamente inmutable no ofrece una manera para que las personas cambien el estado o para tener acceso a las variables de estado que se pueden usar para cambiar el estado. Tu clase no es inmutable como lo es ahora.

Una forma de hacerlo inmutable es devolver una copia de la colección interna, en cuyo caso debe documentarlo muy bien y advertir a la gente sobre su uso en código de alto rendimiento.

Otra opción es usar una colección contenedora que genere excepciones en el tiempo de ejecución si alguien intentara cambiar el valor (no recomendado, pero posible, vea apache-collections para un ejemplo). Creo que la biblioteca estándar también tiene uno (busque en la clase Colecciones).

Una tercera opción, si algunos clientes cambian los datos mientras que otros no, es proporcionar diferentes interfaces para su clase. Digamos que tienes un IMyX e IMyImmutableX. Este último solo define las operaciones "seguras", mientras que el primero lo extiende y agrega las inseguras.

Aquí hay algunos consejos para hacer clases inmutables. http://java.sun.com/docs/books/tutorial/essential/concurrency/imstrat.html

+0

Me gusta mucho la sugerencia de Alex B. Como mencioné en mi publicación, no puedo devolver una copia de la * Lista *. En su respuesta, menciona que no recomienda devolver una vista no modificable de la lista, como sugirió Alex. ¿Puedes por favor elaborar? –

+0

@Scott: Hay un enfoque general en la programación que dice "No sorprenda a sus usuarios", o "prefiera errores de tiempo de compilación a errores de tiempo de ejecución". En general, es preferible que el compilador encuentre un problema en lugar de tener que bloquear el programa de usuario en producción. Con una lista no modificable, se devuelve una lista válida, la lista se puede pasar hasta mucho más tarde de repente puede "explotar" cuando alguien intenta modificarlo. – Uri

+0

@Scott: si necesita devolver una lista y no puede copiarla, entonces el costo que tendrá que tomar es esta excepción de tiempo de ejecución. Pero tal vez considere nombrar su función "getUnmodifiableList" o algo así. Mi investigación demostró que la mayoría de las personas nunca leería realmente los documentos que parecen intuitivos, como data() – Uri

5

¿Se puede usar Collections.unmodifiableList?

Según la documentación, devolverá una vista no modificable (inmutable) del List. Esto evitará el uso de métodos como remove y add lanzando un UnsupportedOperationException.

Sin embargo, no veo que no impida la modificación de los elementos reales en la lista, por lo que no estoy seguro de si esto es lo suficientemente inmutable. Al menos la lista en sí no puede ser modificada.

He aquí un ejemplo en el que los valores internos de la List devuelto por unmodifiableList todavía pueden ser alterados:

class MyValue { 
    public int value; 

    public MyValue(int i) { value = i; } 

    public String toString() { 
     return Integer.toString(value); 
    } 
} 

List<MyValue> l = new ArrayList<MyValue>(); 
l.add(new MyValue(10)); 
l.add(new MyValue(42)); 
System.out.println(l); 

List<MyValue> ul = Collections.unmodifiableList(l); 
ul.get(0).value = 33; 
System.out.println(l); 

Salida:

[10, 42] 
[33, 42] 

Lo que esto demuestra básicamente es que si los datos contenidos en el List es mutable en primer lugar, el contenido de la lista se puede cambiar, incluso si la lista en sí es inmutable.

+0

My List solo contendrá objetos inmutables que amplíen la clase Number (por ejemplo, Integer, Double, etc.). –

+0

Ah, entonces esa es una cosa menos de la que preocuparse :) – coobird

5

Hay varias cosas para hacer que su clase sea inmutable correctamente. Creo que esto se discute en Java efectivo.

Como se menciona en varias otras respuestas, para detener la modificación a list a través del iterador devuelto, Collections.unmodifiableList ofrece una interfaz de solo lectura. Si se trata de una clase mutable, es posible que desee copiar los datos para que la lista devuelta no cambie, incluso si este objeto sí lo hace.

La lista que se pasa al constructor puede modificarse posteriormente, por lo que debe copiarse.

La clase es subclassable, por lo que los métodos se pueden anular. Así que haz la clase final. Mejor proporcionar un método de creación estático en lugar del constructor.

public final class Wrapper implements Iterable<Double> { 
    private final List<Double> list; 

    private Wrapper(List<Double> list) { 
     this.list = Collections.unmodifiableList(new ArrayList<Double>(list)); 
    } 

    public static Wrapper of(List<Double> list) { 
     return new Wrapper(list); 
    } 

    public Iterator<Double> iterator() { 
     return list.iterator(); 
    } 

    public List<Double> data() { 
     return list; 
    } 
} 

También sería útil evitar las pestañas y colocar las llaves en la posición correcta para Java.

+0

+1 para recomendar Java efectivo. :) – cwash

+0

No hay realmente una "posición correcta" para las llaves en Java más que en C. La posición correcta para las llaves es la posición que da como resultado la menor cantidad de errores. –

Cuestiones relacionadas