Effective Java: C#, 5. Evita la creación de objetos innecesarios
En este punto no hay demasiada diferencia entre Java y C#. Lo que vale para uno, vale para otro.
Los objetos inmutables tienen una ventaja: dos o más instancias con el mismo valor pueden compartir memoria. El ahorro no sólo es de memoria: también lo es de tiempo. La creación (y posterior destrucción) de los objetos cuesta tiempo. Las cadenas, por ejemplo, son objetos inmutables. Los literales de cadena que aparecen en nuestro programa reciben un tratamiento especial por el que se garantiza que dos cadenas idénticas se almacenen en memoria una sola vez. Las variables a las que asignamos ese literal de cadena son, pues, referencias a la misma zona de memoria. Es decir, este fragmento de código muestra el valor true por pantalla:
string a = "una cadena"; string b = "una cadena"; Console.WriteLine(object.ReferenceEquals(a, b)); System.Console.Read();
Bloch advierte contra el uso de bucles como éste:
for (int i = 0; i < 100; i++)
{
String a = new String("una cadena");
}
En Java, el bucle crearía 100 instancias diferentes de “una cadena”. En C# no se puede cometer ese error porque el constructor String no está sobrecargado para aceptar una cadena como argumento.
C# ofrece una posibilidad que no encontramos en Java: la definición de structs. Un struct es un tipo valor y la memoria que consumen las variables de tipo valor es de pila, no del heap manejado (salvo cuando el tipo valor está “boxed”, claro está). Un nuevo tipo valor se define como una clase: con campos, propiedades, métodos, etc, pero tiene algunas restricciones y se ha de ir con cuidado con algún cambio semántico en operaciones como la asignación. Una restricción es que no se puede especializar la clase (no hay herencia). Y la asignación de un valor struct almacenado en una variable a otra variable hace una copia de memoria (y no es una simple copia de referencia). Lo mismo ocurre con el paso de tipos valor a funciones como argumento. Esto hace que resulte conveniente limitar los tipos valor a datos con menos de, digamos, una treintena de bytes. Un consejo es que, si se puede, los tipos valor se limiten a codificar valores inmutables. Eso simplifica la semántica de las operaciones. Si los struct se usan para representar objetos inmutables y su tamaño es poco, resultan muy eficientes: no se pide memoria manejada, lo que ahorra el consumo de memoria extra de las referencias que requieren los objetos convencionales, y reducen el tiempo necesario para la gestión de la memoria consumida. Al programa en Java echo de menos la posibilidad de usar tipos valor, especialmente en combinación con genéricos cuando implemento colecciones. Me duele ver el consumo de memoria que supone gestionar una lista de enteros, que en los genéricos de Java pasa por hacer boxing de cada dato. En C# se puede usar una lista de enteros “primitivos” (en la jerga de C# no son primitivos, sino tipos valor). Y se puede, también, definir un tipo valor propio que suministrar como tipo al instanciar una colección genérica. Pero me estoy alejando del tema central. Sigamos.
El resto de la entrada de Bloch se dedica a sugerir que los objetos de tamaño grande o construcción costosa que son de naturaleza “constante” y se crean cada vez que se llama a un método, se creen una sola vez como campos estáticos. La construcción e inicialización de esos objetos se podría hacer en un bloque de código marcado con static. C# permite hacer algo similar, pero con un constructor estático para la clase. Los constructores estáticos no tienen parámetros y se invocan con total garantía antes de que se instancia cualquier elemento de la clase.
Finalmente, Bloch sugiere evitar la construcción de esos objetos si no son absolutamente necesarios. Es decir, podría darse el caso de que construyésemos esos objetos para evitar que las llamadas a un método lo creen una y otra vez, pero que el método en cuestión no se llamara nunca al ejecutar un programa. La construcción del objeto estático hubiese resultado en una pérdida absurda de memoria y tiempo. C# nos auxiliaría aquí con el tipo de datos Lazy<T>.
Finalmente, el autor de Effective Java advierte del peligro que supone el autoboxing inadvertido de tipos primitivos. Puede tener un grave impacto en la eficiencia. El mismo peligro se tiene en C#. En cualquier caso, el peligro es algo menor en C# porque los tipos valor se pueden usar sin “boxing” en estructuras de datos genéricas. No ocurre lo mismo en Java.
Filed under: Effective Java: C# | Deja un comentario

No Responses Yet to “Effective Java: C#, 5. Evita la creación de objetos innecesarios”