¿Por qué las clases de envoltura de Java son inmutables?
immutability primitive-types (9)
Sin embargo, las clases de envoltorio representan tipos primitivos, y los tipos primitivos (excepto String) son mutables.
En primer lugar, String no es un tipo primitivo.
En segundo lugar, no tiene sentido hablar de que los tipos primitivos son mutables. Si cambias el valor de una variable como esta:
int x = 5;
x = 6;
Eso no está cambiando el número 5, está cambiando el valor de x
.
Si bien los tipos de envoltorio podrían haberse hecho mutables, habría sido molesto hacerlo, en mi opinión. Con frecuencia utilizo colecciones de solo lectura de estos tipos y no me gustaría que sean modificables. Muy ocasionalmente quiero un equivalente mutable, pero en ese caso es bastante fácil crear uno, o usar las clases Atomic*
.
Me encuentro deseando que la Date
y el Calendar
fuesen inmutables con mucha más frecuencia de lo que deseo que Integer
sea mutable ... (Por supuesto, normalmente me dirijo a Joda Time, pero uno de los beneficios de Joda Time es la inmutabilidad).
Conozco los motivos habituales que se aplican a las clases generales inmutables, a saber:
- No se puede cambiar como efecto secundario.
- fácil de razonar sobre su estado
- inherentemente seguro para hilos
- no es necesario proporcionar el método de copia de fábrica / copia / construcción
- caché de instancia
- No hay necesidad de copias defensivas.
Sin embargo, las clases de envoltorio representan tipos primitivos, y los tipos primitivos son mutables. Entonces, ¿por qué no son mutables las clases wrapper?
Sin embargo, las clases de envoltorio representan tipos primitivos, y los tipos primitivos (excepto String) son mutables.
No, no lo son (y String no es un primitivo). Pero como los tipos primitivos no son objetos de todos modos, en primer lugar no pueden llamarse mutables / inmutables.
Independientemente, el hecho de que las clases de envoltura sean inmutables es una decisión de diseño (una buena IMO). Thye podría haberse convertido fácilmente en mutable, o también en alternativas mutables proporcionadas (de hecho, varias bibliotecas proporcionan esto y otros idiomas lo hacen por defecto).
Aquí hay un ejemplo donde sería bastante malo que Integer fuera mutable.
class Foo{
private Integer value;
public set(Integer value) { this.value = value; }
}
/* ... */
Foo foo1 = new Foo();
Foo foo2 = new Foo();
Foo foo3 = new Foo();
Integer i = new Integer(1);
foo1.set(i);
++i;
foo2.set(i);
++i;
foo3.set(i);
¿Cuáles son los valores de foo1, foo2 y foo3 ahora? Usted esperaría que fueran 1, 2 y 3. Pero cuando Integer sería mutable, ahora todos serían 3 porque Foo.value
apuntaría al mismo objeto Integer.
Cualquier instancia de objeto que tenga aspectos mutables debe tener una identidad única; de lo contrario, otras instancias de objetos que en algún momento resultaron ser idénticas en todos los sentidos, excepto por su identidad, podrían en algún otro momento ser diferentes en su aspecto mutable. Sin embargo, en muchos casos es útil para los tipos no tener una identidad: poder pasar un "4" sin tener que preocuparse por el "4" que uno está pasando. Si bien hay ocasiones en que puede ser útil tener un envoltorio mutable de un tipo primitivo o inmutable, hay muchas más ocasiones en las que es útil tener un tipo en el que todas las instancias que contienen los mismos datos en algún momento pueden considerarse intercambiable.
Hay envolturas mutables y seguras para subprocesos también para algunos tipos.
AtomicBoolean
AtomicInteger
AtomicIntegerArray
AtomicLong
AtomicLongArray
AtomicReference - can wrap a String.
AtomicReferenceArray
Además de algunas envolturas exóticas
AtomicMarkableReference - A reference and boolean
AtomicStampedReference - A reference and int
Las clases de envoltura son inmutables porque simplemente no tiene sentido ser mutable.
Considere el siguiente código:
int n = 5;
n = 6;
Integer N = new Integer(n);
Al principio, parece sencillo si puede cambiar el valor de N, al igual que puede cambiar el valor de n.
Pero en realidad, N no es una envoltura a n, ¡sino una envoltura a 6! Mira la siguiente línea de nuevo:
Integer N = new Integer(n);
En realidad, está pasando el valor de n, que es 6, a N. Y dado que Java es pasado por valor, no puede pasar n a N, para hacer de N un envoltorio a n.
Por lo tanto, si agregamos un método de ajuste a la envoltura:
Integer N = new Integer(n);
N.setValue(7);
print(N); // ok, now it is 7
print(n); // oops, still 6!
¡El valor de n no cambiará y eso será confuso!
Conclusión:
Las clases envolturas son envolturas de valores, no envolturas de las variables.
será confuso si ha añadido un método set.
Si sabe que es una envoltura de un valor, ya no solicitará un método de configuración. Por ejemplo, no hará "6.setValue (7)".
es imposible hacer una envoltura a una variable en Java.
Los tipos primitivos son mutables, pero no se pueden compartir, es decir, dos secciones del código nunca se referirán a la misma variable int (siempre se pasan por valor). Así que puedes cambiar tu copia y nadie más ve el cambio, y viceversa. Como Phillip muestra en su respuesta, ese no sería el caso con clases de envoltura mutables. Así que mi conjetura es que tenían una opción cuando envolvían los tipos de datos primitivos entre:
haciendo coincidir el hecho de que puede cambiar el valor de un tipo primitivo,
versus
haciendo coincidir el hecho de que los tipos primitivos se pueden pasar y ningún usuario de los datos podrá ver los cambios realizados por un usuario.
Y eligieron lo último, lo que requirió inmutabilidad.
Para su información: si desea clases de titular mutable, puede usar las clases Atomic * en el paquete java.util.concurrent
, por ejemplo, AtomicLong
, AtomicLong
Por ejemplo, considere el siguiente programa java:
class WhyMutable
{
public static void main(String[] args)
{
String name = "Vipin";
Double sal = 60000.00;
displayTax(name, sal);
}
static void displayTax(String name, Double num) {
name = "Hello " + name.concat("!");
num = num * 30 / 100;
System.out.println(name + " You have to pay tax $" + num);
}
}
Result: Hello Vipin! You have to pay tax $18000.0
Este es el caso también con el paso por referencia de los parámetros de la clase wrapper. Y, si las clases de cadenas y envoltorios no son definitivas, cualquiera puede extender esas clases y escribir su propio código para modificar los datos primitivos envueltos. Por lo tanto, para mantener la integridad de los datos, las variables que estamos utilizando para el almacenamiento de datos deben ser de solo lectura.
es decir, las clases de cadenas y envoltorios deben ser finales e inmutables y no se debe proporcionar la función "pasar por referencia".