significa - string inmutable java
¿Cómo gestionar la clase inmutable con LinkedList como un campo de instancia? (6)
Creo que el problema con este enfoque es que todavía puedo hacer getPersons (). Add (x). Para no permitir eso, debe crear un constructor que reciba la lista de personas y luego crear una instancia de su lista final interna como, por ejemplo, ImmutableList . El resto del código me parece bien.
EDITAR:
Como señala Davide Lorenzo MARINO, esto no es suficiente. También deberá devolver una copia detallada de la lista en el getter, de modo que los usuarios no puedan modificar su lista interna por la referencia devuelta por el getter.
Al hacer esto, puedes tener una lista normal de Linkedlist como tu lista interna (final privado). En su constructor, usted crea una copia en profundidad de la Lista y en su receptor devuelve una copia en profundidad de su Lista interna.
Tengo una clase inmutable llamada Employees
como este:
public final class Employees {
private final List<Person> persons;
public Employees() {
persons = new LinkedList<Person>();
}
public List<Person> getPersons() {
return persons;
}
}
¿Cómo mantengo esta clase inmutable?
Hice el campo private
y final
, y no proporcioné un método de establecimiento. ¿Es esto suficiente para lograr la inmutabilidad?
En este caso, yo preferiría no exponer la Lista o el Mapa en absoluto. Si pasa la colección, está rompiendo la encapsulación asumiendo que debe almacenarse como una Lista.
Si no expone la Lista, puede evitar la necesidad de envolverla o proporcionar una copia defensiva.
public final class Employees {
private final List<Person> persons;
public Employees() {
persons = new LinkedList<Person>();
}
public Person getPerson(int n) {
return persons.get(n);
}
public int getPersonCount() {
return persons.size();
}
}
La razón por la que desea evitar las copias defensivas es que pueden llamarse mucho. Alguien podría escribir fácilmente (se sorprendería de la frecuencia con la que se escribe)
for (int i = 0; i < employees.getPersons().size(); i++) {
Person p = employees.getPersons().get(i);
}
Al dar métodos específicos, puede optimizar el código para ese caso de uso.
La respuesta se encuentra en los documentos - Una estrategia para definir objetos inmutables :
No proporcione métodos de "establecimiento": métodos que modifican campos u objetos a los que se hace referencia por campos.
Hacer todos los campos finales y privados.
No permitir que las subclases anulen los métodos.
Si los campos de la instancia incluyen referencias a objetos mutables, no permita que se cambien esos objetos :
4.1. No proporcione métodos que modifiquen los objetos mutables.
4.2. No comparta referencias a los objetos mutables. Nunca almacene referencias a objetos mutables externos pasados al constructor; Si es necesario, cree copias y almacene referencias a las copias. Del mismo modo, cree copias de sus objetos mutables internos cuando sea necesario para evitar devolver los originales en sus métodos.
No, no es suficiente porque en java incluso las referencias se pasan por valor . Entonces, si List''s
referencia de tu List''s
escapa (lo que sucederá cuando llamen a get
), entonces tu clase ya no es inmutable .
Tienes 2 opciones:
- Cree una copia defensiva de su
List
y devuélvala cuando se llame a get. - Envuelva su Lista como una Lista inmutable / No modificable y devuélvala (o reemplace su
List
original con esto, luego puede devolverla de manera segura sin tener que envolverla más)
Nota: Deberá asegurarse de que la Person
sea inmutable o crear copias defensivas para cada Person
en la List
Podrías hacerlo aún mejor con
import java.util.Collections;
...
public List<Person> getPersons() {
return Collections.unmodifiableList( persons);
}
Respuesta editada para explicar no solo el caso con una versión mutable de Person
, sino también con una versión inmutable de Person
.
Tu clase es mutable porque puedes hacer eso:
Employees employees = new Employees();
employees.getPersons().add(new Person());
Tenga en cuenta que si no cambia una lista de personas al constructor, si cambia su código para crear una clase inmutable, tendrá una clase no útil con una lista de personas siempre vacía, por lo que supongo que es necesario pasar una List<Person>
a el constructor
Ahora hay dos escenarios, con diferentes implementaciones:
-
Person
es inmutable -
Person
es mutable
Escenario 1 - La Person
es inmutable.
Solo necesita crear una copia inmutable del parámetro de personas en el constructor .
También necesita getPersons
la clase o, al menos, el método getPersons
para asegurarse de que nadie proporciona una versión sobrescribible del método getPersons
.
public final class Employees {
private final List<Persons> persons;
public Employees(List<Person> persons) {
persons = Collections.unmodifiableList(new ArrayList<>(persons));
}
public List<Employees> getPersons() {
return persons;
}
}
Escenario 2 - La Person
es mutable
getPersons
crear una copia en profundidad de las persons
en el método getPersons
.
Necesitas crear una copia profunda de las persons
en el constructor .
También necesita getPersons
la clase o, al menos, el método getPersons
para asegurarse de que nadie proporciona una versión sobrescribible del método getPersons
.
public final class Employees {
private final List<Persons> persons;
public Employees(List<Person> persons) {
persons = new ArrayList<>();
for (Person person : persons) {
persons.add(deepCopy(person)); // If clone is provided
// and creates a deep copy of person
}
}
public List<Employees> getPersons() {
List<Person> temp = new ArrayList<>();
for (Person person : persons) {
temp.add(deepCopy(person)); // If clone is provided
// and creates a deep copy of person
}
return temp;
}
public Person deepCopy(Person person) {
Person copy = new Person();
// Provide a deep copy of person
...
return copy;
}
}
Esta parte de la respuesta es para mostrar por qué una copia no profunda del personsParameter
pasa al constructor puede crear versiones mutables de los Employees
:
List<Person> personsParameter = new ArrayList<>();
Person person = new Person();
person.setName("Pippo");
personsParameter.add(person);
Employees employees = new Employees(personsParameter);
// Prints Pippo
System.out.println(employees.getPersons().get(0).getName());
employees.getPersons().get(0).setName("newName");
// Prints again Pippo
System.out.println(employees.getPersons().get(0).getName());
// But modifiyng something reachable from the parameter
// used in the constructor
person.setName("Pluto");
// Now it prints Pluto, so employees has changed
System.out.println(employees.getPersons().get(0).getName());