getters - encapsulation java
¿La modificación del resultado de un captador afecta al objeto en sí? (6)
Tengo una pregunta sobre el uso de métodos getter en java. Supongamos que tengo esta clase:
class Test {
private ArrayList<String> array = new ArrayList<String>();
public ArrayList getArray() {
return this.array;
}
public void initArray() {
array.add("Test 1");
array.add("Test 2");
}
}
class Start {
public static void main(String args[]) {
initArray();
getArray().remove(0);
}
}
Mi pregunta es:
¿Se modificará el objeto de arraylist real ("La prueba 1" se eliminará de él)? Creo que he visto esto en algunos lugares, pero pensé que los captadores simplemente proporcionaban una copia de ese objeto. No es una referencia a ello. Si funcionara de esa manera (como referencia), entonces, ¿esto también funcionaría (el objeto de arrailista de la prueba de clase también se vería alterado por esto)?
class Start {
public static void main(String args[]) {
initArray();
ArrayList aVar = getArray();
aVar.remove(0);
}
}
Como han dicho otros, a menos que sea un tipo primitivo, obtendrá una referencia al objeto. Es similar a un puntero en C ++, le permite acceder al objeto, pero a diferencia de la referencia de C ++ (puntero a la dirección de memoria de una variable) no le permite reemplazarlo por otro objeto. Sólo el acomodador puede hacer eso.
Veo dos variantes en su pregunta, test.getArray().remove(0)
y aVar.remove(0)
. No hay diferencia en los resultados de estos, es solo una referencia similar a un puntero y modifica el original.
Nunca se obtiene un clon simplemente llamando a un captador, por lo que, a menos que el objeto sea inmutable, puede modificar el objeto al que el autor de acceso le dio acceso. Por ejemplo, String
es inmutable, cualquier Collection
básica (incluida ArrayList
) es mutable. Puede llamar a Collections.unmodifiable*(...)
para que una colección no sea modificable. Sin embargo, si los elementos de la colección son mutables, todavía se pueden cambiar.
En algunos casos, obtener un clon es una buena idea, en la mayoría de los casos no lo es. Un captador no debería clonar nada en absoluto, ni siquiera debería modificar los datos a menos que inicialice una colección posiblemente nula o algo así. Si desea una colección no modificable que contenga objetos inmutables, intente hacerlo de esta manera. En este ejemplo tenemos una clase FooImpl
que implementa la interfaz Foo
, las razones que se explicarán más adelante.
public interface Foo {
int getBar();
}
public class FooImpl Foo {
private int bar;
@Override
public int getBar() {
return bar;
}
public void setBar(int newValue) {
this.bar = newValue;
}
}
Como ves, Foo no tiene setter. Si crea un ArrayList<Foo>
y lo pasa de algún captador como Collections.unmodifiableList(myArrayList)
, casi parece que lo hizo. Pero el trabajo aún no está hecho. Si la clase FooImpl
es pública (que es en este caso), alguien podría intentarlo si la persona que encontró en la lista es una instanceof FooImpl
y luego la (FooImpl) foo
en mutable. Sin embargo, podemos envolver cualquier Foo
en un envoltorio llamado FooWrapper
. Implementa Foo
también:
public class FooWrapper implements Foo {
private Foo foo;
public FooWrapper(Foo foo) {
this.foo = foo;
}
public int getBar() {
return foo.getBar();
}
// No setter included.
}
Luego podemos poner un new FooWrapper(myFoo)
en una Collection<FooWrapper>
. Esta envoltura no tiene ningún setter público y el interior es privado. No puede modificar los datos subyacentes. Ahora sobre esa interfaz Foo
. Tanto FooImpl
como FooWrapper
implementan, si algún método no pretende modificar los datos, puede solicitar la entrada de Foo
. No importa qué Foo
consigues.
Entonces, si desea una colección no modificable que contenga datos no modificables, FooWrapper
una nueva Collection<Foo>
, aliméntela con objetos FooWrapper
y luego llame a Collections.unmodifiable*(theCollection)
. O haga una colección personalizada que envuelva toda la colección de Foo, devolviendo FooWrappers, por ejemplo, esta lista:
public MyUnmodifiableArrayList implements List<Foo> {
ArrayList<Foo> innerList;
public get(int index) {
Foo result = innerList.get(index);
if (!(result instanceof FooWrapper)) {
return new FooWrapper(result);
}
return result; // already wrapped
}
// ... some more List interface''s methods to be implemented
}
Con la colección envuelta, no tiene que recorrer la colección original y hacer su clonación con envoltorios de datos. Esta solución es mucho mejor cuando no la lees completa, pero crea un FooWrapper nuevo cada vez que llamas a get()
, a menos que el Foo en ese índice ya sea un FooWrapper
. En un hilo de larga ejecución con millones de llamadas a get()
, esto podría convertirse en un punto de referencia innecesario para el recolector de basura, haciendo que uses una matriz interna o un mapa que contenga FooWrappers ya existentes.
Ahora puede devolver la nueva List<Foo>
personalizada List<Foo>
. Pero una vez más, no de un letrero. getUnmodifiableFooList()
en algo como getUnmodifiableFooList()
para su campo private ArrayList<FooImpl> fooList
.
Como se señaló, su captador no modifica la lista, devuelve una referencia modificable a la lista. Las herramientas como Findbugs te lo advertirán ... puedes vivir con eso y confiar en los usuarios de tu clase para que no apaguen tu lista, o usar esto para devolver una referencia no modificable a tu lista:
public static List<String> getArray() {
return Collections.unmodifiableList(array);
}
De la forma en que lo entiendo, las variables de referencia de objetos son poco más que direcciones de memoria de los propios objetos. Entonces, lo que se devuelve desde getArray () es una variable de referencia para ese ArrayList. Un objeto puede tener muchas variables de referencia, pero sigue siendo el mismo objeto que se modifica.
Java hace que todo pase por valor. Por lo tanto, cada vez que pasa una variable de referencia de objeto como parámetro o devuelve su valor, está pasando o devolviendo el valor de la variable de referencia de objeto.
Java devuelve referencias al Array, por lo que no será una copia y modificará la Lista. En general, a menos que sea un tipo primitivo ( int
, float
, etc.) obtendrá una referencia al objeto.
Debe copiar explícitamente la matriz si desea que se devuelva un duplicado.
Para responder a su pregunta, con un captador usted obtiene acceso directo a una variable. Ejecute este código y podrá ver que se elimina la cadena en el ArrayList. Pero no use un ArraList estático como este ejemplo en su código.
public class Test {
private static ArrayList<String> array = new ArrayList<String>();
public static ArrayList<String> getArray() {
return array;
}
public static void initArray() {
array.add("Test 1");
array.add("Test 2");
}
public static void main(String[] args) {
initArray();
ArrayList aVar = getArray();
aVar.remove(0);
System.out.println(aVar.size());
}
}
Que un captador no modifique el objeto al que se llama es simplemente una cuestión de convención. Ciertamente no cambia la identidad del objetivo, pero puede cambiar su estado interno. Aquí hay un ejemplo útil, aunque un poco vago:
public class Fibonacci {
private static ConcurrentMap<Integer, BigInteger> cache =
new ConcurrentHashMap<>();
public BigInteger fibonacci(int i) {
if (cache.containsKey(i)) {
return cache.get(i);
} else {
BigInteger fib = compute(i); // not included here.
cache.putIfAbsent(i, fib);
return fib;
}
}
Por lo tanto, llamar a Fibonacci.fibonacci(1000)
puede cambiar el estado interno del objetivo, pero sigue siendo el mismo objetivo.
Ahora, aquí hay una posible violación de seguridad:
public class DateRange {
private Date start;
private Date end;
public DateRange(final Date start, final Date end) {
if (start.after(end)) {
throw new IllegalArgumentException("Range out of order");
}
this.start = start;
this.end = end;
}
public Date getStart() {
return start;
}
// similar for setStart, getEnd, setEnd.
}
El problema es que java.lang.Date
es mutable. Alguien puede escribir código como:
DateRange range = new DateRange(today, tomorrow);
// In another routine.
Date start = range.getStart();
start.setYear(2088); // Deprecated, I know. So?
Ahora el rango está fuera de orden. Es como darle al cajero su billetera.
Por eso es mejor hacer uno de estos, siendo preferibles los anteriores.
- Tener tantos objetos como sea posible ser inmutable. Esta es la razón por la que se escribió Joda-Time y por qué las fechas volverán a cambiar en Java 8.
- Haga copias defensivas de los artículos que uno pone u obtiene.
- Devuelve un envoltorio inmutable de un artículo.
- Devolver colecciones como iterables, no como ellos mismos. Por supuesto, alguien podría devolverlo.
- Devuelve un proxy para acceder al elemento, que no se puede convertir a su tipo.
Sé que sé. Si quiero C o C ++, sé dónde encontrarlos. 1. Regreso