제이온

[Effective Java] Item 6. Evita la creación innecesaria de objetos

Creado: 2024-04-28

Creado: 2024-04-28 13:40

Casos en los que se crean objetos innecesarios

Uso de new String()


Las cadenas a, b y c contienen todas la cadena "hi". Sin embargo, como las direcciones a las que hacen referencia estas tres cadenas son diferentes, se produce un desperdicio al asignar diferentes memorias para los mismos datos.


<span class="image-inline ck-widget" contenteditable="false"><img src="https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Fa53d14aa-6abf-40f0-8441-435647d172fa%2FUntitled.png?table=block&id=f168859c-367c-4924-a4c6-5e04788fab67&spaceId=b453bd85-cb15-44b5-bf2e-580aeda8074e&width=2000&userId=80352c12-65a4-4562-9a36-2179ed0dfffb&cache=v2" alt="Untitled" style="aspect-ratio:2000/1146;" width="2000" height="1146"></span>


Por lo tanto, al declarar una cadena, en lugar de usar la palabra clave new, se debe declarar utilizando un literal.



El código fuente anterior utiliza una sola instancia. Además, al usar este método, se garantiza que todo el código que use el literal de cadena "hi" dentro de la misma JVM reutilizará el mismo objeto. Esto se debe a una característica del pool de constantes de Java.


Uso de new Boolean()

El código anterior crea una instancia de Boolean a través del constructor que recibe una cadena como parámetro. Boolean solo puede tener los valores true o false, por lo que crear una instancia cada vez supone un desperdicio de memoria. Por lo tanto, es mejor usar el método de fábrica estático Boolean.valueOf().



Uso de String.matches()

Si el coste de creación es alto, es recomendable almacenarlo en caché y reutilizarlo, pero no siempre podemos conocer el coste de los objetos que creamos. Por ejemplo, si queremos escribir un método que compruebe si una cadena dada es un número romano válido, usar una expresión regular es la forma más sencilla.



Sin embargo, String.matches() es un método que presenta problemas de rendimiento. La instancia Pattern para expresiones regulares que este método crea internamente se usa una vez y se descarta, convirtiéndose inmediatamente en un objetivo para la recolección de basura. Cuanto mayor sea la frecuencia con la que se utiliza la expresión regular, mayor será el coste de crear y descartar la misma instancia Pattern. Por lo tanto, es recomendable almacenar en caché la instancia Pattern previamente y reutilizarla cada vez que se llame al método isRomanNumeral().



Precaución

En todos los ejemplos anteriores, al almacenar en caché objetos innecesarios, se han creado objetos inmutables. Esto se debe a que, de esta manera, la reutilización es segura. Sin embargo, hay casos en los que la reutilización de objetos inmutables va en contra de la intuición.


Un adaptador (vista) es un objeto que delega la tarea real a un objeto de fondo y actúa como una segunda interfaz. Dado que el adaptador solo necesita gestionar el objeto de fondo, basta con crear un adaptador por cada objeto de fondo.


Por ejemplo, el método keySet() de la interfaz Map devuelve una vista Set que contiene todas las claves del objeto Map. Los usuarios pueden pensar que se crea una nueva instancia de Set cada vez que se llama al método keySet(), pero si se observa la implementación real de JDK, se devuelve la misma instancia de Set mutable cada vez.


Esto se debe a que, aunque la instancia Set devuelta es mutable, las funciones que realiza son las mismas y todas las instancias Set representan al objeto Map. Por lo tanto, no hay problema en que keySet() cree varias vistas de objetos, pero tampoco hay necesidad ni ventaja en hacerlo.



Por lo tanto, si modificamos la instancia names1, la instancia names2 también se verá afectada.


Sin embargo, personalmente creo que el valor de retorno del método keySet() debería utilizar una copia defensiva para devolver una nueva instancia cada vez. Si la instancia Set recibida mediante el método keySet() también se utiliza en otros lugares y hay código que modifica el estado de esta instancia, no se puede estar seguro del valor de la instancia Set actual y la instancia Map.


Además, a menos que el entorno utilice keySet() excesivamente, la creación repetida de la interfaz Set no tendrá un impacto significativo en el rendimiento. Creo que es mejor hacer que la interfaz Set sea un objeto inmutable para que el mantenimiento sea estable.


Autoboxing

El autoboxing es una técnica que convierte automáticamente entre tipos primitivos y tipos envoltorios cuando un programador los mezcla. Sin embargo, el autoboxing solo difumina la distinción entre tipos primitivos y tipos envoltorios, no los elimina por completo.



Lógicamente, no hay ningún problema, pero el código es muy ineficiente en términos de rendimiento. La causa de esto se encuentra en los tipos de sum y i dentro del bucle for.


El tipo de sum es Long y el de i es long. Es decir, cada vez que i, que es de tipo long, se suma a sum durante la iteración del bucle, se crea una nueva instancia de Long. Como resultado, es mejor usar tipos primitivos en lugar de tipos envoltorios y tener cuidado de no usar autoboxing sin darse cuenta.


Partes que no se deben malinterpretar

No se debe malinterpretar que evitar la creación innecesaria de objetos simplemente significa que se debe evitar la creación de objetos porque el coste de la creación de objetos es alto.


En particular, en las JVM actuales, la creación y eliminación de pequeños objetos creados innecesariamente no es una tarea muy onerosa. Por lo tanto, a menos que se trate de objetos con un coste muy alto, como las conexiones a bases de datos, no cree pools de objetos personalizados.


Además, recuerde que el daño que se produce al reutilizar objetos en situaciones en las que se necesita una copia defensiva es mucho mayor que el daño que se produce al crear objetos innecesarios repetidamente. Los efectos secundarios de la creación repetida solo afectan a la forma y el rendimiento del código, pero si la copia defensiva falla, se produce un error o un problema de seguridad.


Fuente

Comentarios0