![translation](https://cdn.durumis.com/common/trans.png)
Esta es una publicación traducida por IA.
[Effective Java] Item 6. Evita la creación innecesaria de objetos
- Idioma de escritura: Coreano
- •
-
País de referencia: Todos los países
- •
- Tecnología de la información
Seleccionar idioma
Texto resumido por la IA durumis
- Crear instancias de String o Boolean usando la palabra clave new es un desperdicio de memoria, por lo que es mejor declararlas como literales o usar el método Boolean.valueOf().
- El método String.matches() usa expresiones regulares, por lo que puede haber problemas de rendimiento, y es mejor almacenar en caché las instancias de Pattern y reutilizarlas.
- Cuando se devuelven objetos de vista, como el método keySet(), es más seguro usar una copia defensiva para devolver un nuevo objeto.
Cuando se crean objetos innecesarios
Usando new String()
String a = new String("hi");
String b = new String("hi");
Las cadenas a, b, c todas tendrán la cadena "hi". Sin embargo, dado que las direcciones a las que hacen referencia estas tres cadenas son diferentes, se produce un desperdicio al asignar memoria diferente para los mismos datos.
Por lo tanto, al declarar una cadena, debe usar un literal en lugar de la palabra clave new.
String a = "hi";
String b = "hi";
El código fuente anterior usa solo una instancia. Además, al usar este método, se garantiza que todo el código que usa el literal de cadena "hi" dentro de la misma JVM reutilice el mismo objeto. Esto se debe a la característica de la agrupación de constantes de Java.
Usando new Boolean()
El código anterior crea una instancia de Boolean a través de un constructor que recibe una cadena como parámetro. Boolean solo puede ser true o false, y crear una instancia cada vez es una pérdida de memoria. Por lo tanto, es mejor usar el método de fábrica estático Boolean.valueOf().
Usando String.matches()
Si el costo de creación es alto, es mejor almacenarlo en caché y reutilizarlo, pero no siempre podemos saber el costo del objeto que estamos creando. Por ejemplo, si queremos escribir un método que verifique si una cadena dada es un número romano válido, el uso de expresiones regulares es la forma más fácil, como se muestra a continuación.
public static boolean isRomanNumeral(String s) {
return s.matches("^(?=.)M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
Sin embargo, String.matches() es un método problemático en términos de rendimiento. La instancia de Pattern creada internamente por este método para la expresión regular se utiliza una vez y se descarta, convirtiéndose inmediatamente en un objetivo de la recolección de basura, pero a medida que aumenta la frecuencia con la que se utiliza la expresión regular en particular, el costo de crear y descartar la misma instancia de Pattern aumenta. Por lo tanto, es mejor almacenar en caché la instancia de Pattern por adelantado y reutilizarla cada vez que se llame al método isRomanNumeral() más adelante.
public class RomanNumerals {
private static final Pattern ROMAN = Pattern.compile(
"^(?=.)M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
public static boolean isRomanNumeral(String s) {
return ROMAN.matcher(s).matches();
}
Nota
En todos los ejemplos anteriores, cuando se almacenaba en caché un objeto innecesario, todos se crearon como objetos inmutables. Esto se debe a que es seguro reutilizarlos. Sin embargo, hay casos en los que la reutilización contradice la intuición de los objetos inmutables.
Un adaptador (vista) es un objeto que delega el trabajo real a un objeto de back-end y actúa como una segunda interfaz. Un adaptador solo necesita administrar el objeto de back-end, por lo que solo necesita crear un adaptador por cada objeto de back-end.
Por ejemplo, el método keySet() de la interfaz Map devuelve una vista Set que contiene todas las claves dentro 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 al ver el código de implementación real de JDK, devuelve la misma instancia de Set variable cada vez.
Esto se debe a que incluso si la instancia de Set devuelta es variable, la función que realiza es la misma, y todas las instancias de Set representan la instancia de Map. Por lo tanto, no importa si keySet() crea varias vistas de objetos, pero no hay necesidad ni beneficio.
public class UsingKeySet {
public static void main(String[] args) {
Map menu = new HashMap<>();
menu.put("Burger", 8);
menu.put("Pizza", 9);
Set names1 = menu.keySet();
Set names2 = menu.keySet();
names1.remove("Burger");
System.out.println(names1.size()); // 1
System.out.println(names2.size()); // 1
}
Por lo tanto, si modifica la instancia names1, la instancia names2 también se verá afectada.
Sin embargo, personalmente creo que el valor devuelto por el método keySet() debe usar una copia defensiva para devolver un nuevo objeto cada vez. Si la instancia de Set recibida por el método keySet() también se está utilizando en otro lugar y hay código que cambia el estado de esta instancia, ya no podrá confiar en el valor de la instancia de Set y la instancia de Map.
Además, a menos que el método keySet() se use excesivamente, el hecho de que la interfaz Set se cree cada vez no afectará fatalmente al rendimiento. Creo que es mejor hacer que la interfaz Set sea un objeto inmutable para que se pueda mantener de forma estable y segura.
Autoboxing
El autoboxing es una técnica que convierte automáticamente entre tipos de datos primitivos y tipos de envoltura cuando el programador usa ambos tipos de datos de forma mixta. Sin embargo, el autoboxing solo desdibuja la distinción entre tipos de datos primitivos y tipos de envoltura, no los elimina por completo.
public static long sum() {
Long sum = 0L;
for (long i = 0; i <= Integer.MAX_VALUE; i++) {
sum += i;
}
return sum;
Lógicamente no hay problema, pero es un código muy ineficiente en términos de rendimiento. La razón es el tipo de sum y el tipo de i dentro del ciclo for.
El tipo de sum es Long, y 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 necesario utilizar tipos de datos primitivos en lugar de tipos de envoltura y tener cuidado para evitar el autoboxing no deseado.
Puntos que no deben malinterpretarse
No debe malinterpretarse que la necesidad de evitar la creación de objetos innecesarios se debe simplemente al alto costo de la creación de objetos.
Especialmente en las JVM modernas, crear y recuperar objetos pequeños innecesariamente no es una tarea tan costosa. Por lo tanto, a menos que sean objetos muy costosos como las conexiones a bases de datos, no cree un grupo de objetos personalizado.
Además, recuerde que el daño de reutilizar objetos cuando se necesita una copia defensiva es mucho mayor que el daño de crear objetos innecesarios repetidamente. Los efectos secundarios de la creación repetida solo afectan la forma del código y el rendimiento, pero si la copia defensiva falla, irá directamente a errores y problemas de seguridad.