¡No uses new en Java!

Siempre prefiere static factory methods al uso de constructores.

Hay dos circunstancias distintas donde debemos seguir esta máxima: cuando creamos una clase y definimos cómo se van a crear sus instancias, y cuando creamos instancias de otras clases que no hemos definido nosotros.

Creo que la mayor parte del tiempo tenemos esto presente cuando estamos definiendo nuestra clase. Y no tanto cuando creamos objetos de otras. En especial si esa clase tiene constructores públicos.

Clases no propias

Es por eso que primero quiero enfocarme en ese caso. Debemos acostumbrarnos a buscar siempre métodos estáticos que pueda proveernos la clase para crear sus instancias. Porque no sólo nos quitara cierta responsabilidad o nos evitará cierto esfuerzo de mantenimiento en el futuro. Sino porque algunas clases implementan internamente un mecanismo de cache. Y nos perderíamos de ese beneficio si creamos instancias con constructores. Veamos algunos ejemplos:

String

String str1 = new String("Hola");
String str2 = new String("Hola");

System.out.println(str1 == str2);

String str3 = String.valueOf("Hola");
String str4 = String.valueOf("Hola");

System.out.println(str3 == str4);

El bloque de código de arriba nos dar la siguiente salida:

false
true

Nótese que estamos comparando las referencias y no estamos haciendo una comparación lógica con equals(). Esto se debe a que nos interesa saber si se trata de dos referencias a una misma instancia o dos instancias distintas. Y no queremos probar si el contenido de las cadenas es el mismo. Eso ya lo sabemos.

El motivo por el que obtenemos una misma instancia al utilizar valueOf() en lugar del constructor es que ese static factory method que llamamos en las líneas seis y siete implementa un cache que nos devolverá una instancia ya existente de la clase si se trata de una cadena para la cual esa instancia existe. En cambio cuando usamos el operador new como en las dos primeras líneas estamos obligando al compilador a crear un nueva instancia de la clase.

En aplicaciones que carguen muchos cadenas a memoria y donde la repetición de las mismas sea una posibilidad, esto puede hacer una gran diferencia a la hora del uso de memoria y el costo de procesamiento para la creación de instancias.

Integer

De forma similar a lo que ocurre con String las clases numérica suelen tener algún cache para una cantidad limitada de valores:

Integer val1 = new Integer(127);
Integer val2 = new Integer(127);
System.out.println(val1 == val2);
Integer val3 = 127;
Integer val4 = 127;
System.out.println(val3 == val4);

El primer bloque nos dirá que se trata de instancias distintas tal como explicamos anteriormente, debido al uso de new. Sin embargo no ocurre lo mismo con las líneas seis y siete. Aunque aquí no estamos llamando al método estático. Y es que entra en juego otra característica de Java llamada autoboxing. Según la cual el lenguaje se toma el trabajo de llamar al método valueOf() de la clase ante la presencia de un literal. Por supuesto esto funciona con clases cuyos valores pueden escribirse como literales en Java. Es decir que a los fines prácticos estas líneas seis y siete son equivalentes a la del ejemplo de String. E incluso podríamos hacer lo mismo en ese caso.

Integer val3 = 128;
Integer val4 = 128;

La limitación en el caso de la clase Integer está en que el cache almacena 256 valores (desde -128 a +127) solamente. Por lo que según el ejemplo de arriba lo que obtendremos son dos instancias diferentes por el simple hecho de usar un valor por encima del 127.

Es importante aclarar que este comportamiento es completamente dependiente de la implementación de estas clases en la biblioteca estándar. Si la documentación de la clase o la biblioteca menciona la existencia del cache, podemos asumir que siempre estará presente. Pero puede haber casos en que el cache esté implementado sin que la documentación se comprometa al mismo. Y eso implicaría que podría ser eliminado sin advertencia en un futuro. Así que hay que tener cuidado si nuestro código asumirá la existencia del cache.

Otras clases

La clase Long también recomienda utilizar valueOf() en caso de no requerir una nueva instancia. Pero la documentación hace una aclaración importante:

JavaDoc for JDK 1.8

Lo que dice es que este método tendrá en el cache los valores más utilizados. Pero que a diferencia de la clase Integer no está obligada a mantener un cache de los valores dentro de cierto rango. Es decir, no podemos asumir que si pedimos la instancia de Long de valor 1 se tratará de una instancia en el cache. Al contrario de lo que pasa con Integer.

La clase BigInteger tiene un cache para valores entre -16 y +16 pero su documentación no especifica el rango. Sólo dice que provee un cache. La clase Double, en la versión 1.8 al menos, es un caso interesante ya que la documentación dice que proveerá un cache y sin embargo este es el cuerpo del método:

public static Double valueOf(double d) {
    return new Double(d);
}

Claramente no hay un cache y esto nos dará una nueva instancia de la clase. La lección aquí es que muchas veces no alcanza con ver la documentación, sino que es necesario ver el código fuente.

Boolean tiene dos instancias (por supuesto) que nos retorna si usamos el método estático. Character tiene un cache de 0 a 127.

Clases propias

Cuando creamos nuestras clases es importante que utilicemos static factory methods siempre que tenga sentido. En primer lugar porque podríamos implementar un cache como lo hacen las clases que mencionamos en la primera parte del artículo. Pero también por las otras ventajas que podemos obtener.

Si nuestra clase tiene una complejidad mediana o alta, o si cierto procesamiento de los valores recibidos es necesario, utilizar un método estático nos provee el espacio para realizar estas tareas. Y si por algún motivo la creación no es posible, es mucho más natural arrojar una excepción desde un método. Pero por sobre todo vamos a encapsular esta complejidad en ese método y nos permitirá luego mantenerla con el costo de modificar este detalle en un sólo lugar. A diferencia de lo que ocurriría si el usuario de nuestra clase tuviese que hacer eso en cada parte del código que construye una instancia de nuestra clase.

Adicionalmente podemos usar este patrón para una jerarquía de clases donde queremos la libertad de poder modificar la instancia concreta que se fabricará en el futuro. Es decir, podemos declarar un método estático que tiene como tipo de retorno una interfaz. Y el cuerpo del método retorna un objeto de una clase que implementa esa interfaz.

Impresora crearImpresora() {
  return new ImpresoraLaser();
}

Luego será muy fácil modificar la clase concreta que se retorna sin que quienes llaman a este método sepan lo que está ocurriendo.

Impresora crearImpresora() {
  return new ImpresoraTermica();
}

No importa en cuantos lugares estemos llamando al método. Sólo modificando una línea nuestro sistema empezará a utilizar una clase distinta.

Este patrón puede implementarse de una forma más compleja aún. Y nos permite mayor flexibilidad o más formalidad. Pero ese caso de uso escapa a la intención de este artículo.

Fin

Espero que la nota haya sido útil. Te invito a dejar tu comentario o crítica aquí abajo.