Java: ¿Por qué no me funciona la comparación de cadenas? Literales, clases y el repositorio de cadenas
Tags: java, spanish
Published: 2019-11-13 18:13:01

---
title: Java: ¿Por qué no me funciona la comparación de cadenas? Literales, clases y el repositorio de cadenas
published: true
date: 2019-10-29 08:00 UTC
tags: Java, Spanish
cover_image: https://www.campusmvp.es/recursos/image.axd?picture=/2019/4T/java-cadenas-literales.png
canonical_url: https://www.campusmvp.es/recursos/post/java-comparacion-de-cadenas-literales-clases-repositorio-de-cadenas.aspx
---

Una pregunta habitual de los que comienzan con **Java** (e incluso en entrevistas de trabajo para puestos que usan este lenguaje) tiene que ver con las comparaciones entre cadenas de texto. Si vienes de otros lenguajes puedes estar acostumbrado a comparar cadenas con el operador igualdad `==` o, simplemente, te puede parecer la forma más evidente y obvia de hacerlo.

Sin embargo, consideremos un código como este:

```
String s1 = new String("campusMVP");
String s2 = new String("campusMVP");
System.out.println(s1 == s2); //Devuelve false

```

Aunque las cadenas son idénticas y obtienes dos objetos `String` idénticos, la comparación devuelve `false` 🤔

En este otro ejemplo similar:

```
String s1 = "campusMVP";
String s2 = new String("campusMVP");
System.out.println(s1 == s2); //Devuelve false

```

devuelve `false` también.

Sin embargo esto:

```
String s1 = "campusMVP";
String s2 = "campusMVP";
System.out.println(s1 == s2); //Devuelve true

```

devuelve `true`.

Es más, un caso más común sería el de comparar el resultado de una función que devuelve una cadena, con otra cadena cualquiera. Algo así:

```
String s1 = "OK";
if (s1 == FuncionQueDevuelveCadena()) {
    System.out.println("¡Coinciden!");
}
else {
    System.out.println("No coinciden"));
}

```

Bien, lo curioso del fragmento anterior es que, **dependiendo de lo que haga la función y aunque la cadena devuelta sea exactamente la misma** en ambos casos, **el resultado puede ser `true` o `false`**.

¿A qué es debido esto?

## El funcionamiento de las cadenas en Java - Repositorio o `Pool` de cadenas

Las cadenas en Java son una especie aparte. Tienen muchos detalles que es preciso conocer para entenderlas (por ejemplo, que [son inmutables](https://www.campusmvp.es/recursos/post/cadenas-mutables-e-inmutables-en-java-cuando-usar-string-stringbuilder-y-stringbuffer.aspx), o `Final` en Java, o que internamente se pueden [codificar en memoria de varias formas](https://www.campusmvp.es/recursos/post/cadenas-compactas-en-java-9.aspx)). Conocerlas bien es un paso indispensable para dominar el lenguaje.

Un concepto sencillo una vez que lo entiendes, pero que no todo el mundo tiene claro es **la forma de instanciarlas** dependiendo de cómo se declaren.

Básicamente tenemos dos maneras de declarar una cadena: con un literal o instanciando una clase:

```
String literal = "campusMVP";
String clase = new String("campusMVP");

```

En ambos casos lo que obtenemos es lo mismo: una clase `String` con una cadena dentro (que no es más que una matriz de caracteres en memoria). Sin embargo, hay una gran diferencia entre hacerlo de una forma o la otra.

Cuando se declara una cadena de manera **literal** por primera vez, [la JVM](https://www.campusmvp.es/recursos/post/que-es-la-maquina-virtual-de-java-o-java-virtual-machine.aspx) la coloca en un espacio especial denominado **repositorio de cadenas** ( **_string pool_** en inglés). Este repositorio contiene una **copia única** de las diferentes cadenas declaradas como literales en el código. Aquí la palabra importante es "única". Como las cadenas son inmutables, si declaras más de una vez la misma cadena no tiene sentido tenerla varias veces en memoria, así que **la siguiente vez que la declares** lo que hace la JVM es ir al repositorio de cadenas y localizarla, **devolviendo una referencia al mismo objeto `String`** que en la primera declaración. OJO: es el mismo objeto, no una copia. Por eso al escribir esto:

```
String s1 = "campusMVP";
String s2 = "campusMVP";
String s3 = new String(s1);
System.out.println(s1 == s2); //true
System.out.println(s1 == "campusMVP"); //true

```

En todos los casos la comparación se realiza con éxito. Lo que ocurre es que **en todos los casos se usa el mismo objeto `String` exactamente**. En la línea 1, en la primera declaración, se almacena en el repositorio de cadenas. En la línea 2, la segunda declaración del literal, la JVM va al repositorio y mira si ya existe la misma cadena de antes. Como en este caso sí que existe, lo que hace es devolver una referencia a la misma cadena que en la línea 1. ¡Por eso son iguales! Porque no solo representan los mismos caracteres, sino que son de hecho la misma clase (y apuntan a la misma matriz en memoria). En la línea 4, cuando se hace la comparación en `s1` y el mismo literal, vuelve a pasar lo mismo: la JVM localiza el literal en el repositorio de cadenas y devuelve la misma referencia, por eso es `true` de nuevo.

Se puede ver visualmente mejor en esta figura, que trata de representar la situación:

![El repositorio de cadenas de Java](https://www.campusmvp.es/recursos/image.axd?picture=/2019/4T/java-cadenas-literales.png)

Esto funciona incluso con la combinación de varios literales. La JVM es lo suficientemente inteligente como para hacer que estas dos cadenas sean la misma:

```
String s1 = "campusMVP";
String s2 = "campus" + "MVP";
System.out.println(s1 == s2); //true

```

A pesar de que podría parecer que hay 3 cadenas en el repositorio de cadenas, y que devolvería dos objetos diferentes.

## Declaración de cadenas como clases

Sin embargo, cuando declaramos una cadena usando una clase (con `new String()`), la operación es diferente. Lo que se hace es asignar en memoria el espacio necesario para la cadena y devolver la clase que permite manejarla. **No se pasa por el repositorio de cadenas** para nada.

Por ello este fragmento:

```
String s1 = new String("campusMVP");
String s2 = new String("campusMVP");
System.out.println(s1 == s2); //Devuelve false

```

devuelve `false` en la comparación. Se trata de dos cadenas diferentes, aunque internamente representen los mismos caracteres:

![Imagen que muestra dos cadenas iguales, pero en dos posiciones de memoria diferentes](https://www.campusmvp.es/recursos/image.axd?picture=/2019/4T/java-cadenas-clases.png)

> **Nota** : lo sé, es absurdo declarar cadenas de esta manera envolviendo un literal con una clase `String`. Y de hecho, raramente lo verás hecho por ahí. Pero es que hay infinidad de maneras de obtener clases `String` en una variable: desde transformar un literal hasta sacarlas de una base de datos. Solo pongo el ejemplo de esta manera para que se entienda lo que quiero explicar, no para indicar que esta es una manera recomendada de declarar las cadenas.

¿Qué ocurre si tenemos dos cadenas literales y queremos compararlas con `==` sin considerar mayúsculas y minúsculas?

Podríamos hacer esto:

```
String s1 = "campusMVP";
String s2 = "campusMVP";
System.out.println(s1.toLowerCase() == s2.toLowerCase()); //false!

```

En este caso la comparación devuelve `false`, ya que lo que obtenemos son dos cadenas idénticas pero representadas por dos objetos diferentes. **El operador `==` compara referencias a objetos** y en este caso, aunque los datos que contienen son los mismos, no se trata del mismo objeto. Incluso siendo `s1` y `s2` el mismo objeto `String,`la operación `toLowerCase()` lo que hace es generar una nueva cadena a partir de la misma cadena original, pero se trata de dos objetos `String` diferentes en memoria. Es como el caso anterior. Al no ser literales, no se obtienen desde el repositorio de cadenas aunque ya existan allí.

Por eso es tan peligroso comparar cadenas con `==`. Aunque las cadenas sean idénticas, como lo que se comparan son referencias a objetos `String` y no el valor subyacente, solo devuelven `true` para cadenas iguales cuando se refieren al mismo literal.

> **Conclusión** : no utilices nunca el operador `==` para comparar cadenas si quieres hacerlo bien en Java.

## Y entonces ¿cómo comparo cadenas con seguridad?

Pues **con el método `equals()`** que tienen todos los objetos. De hecho, es heredado de `Object`, la clase base "raíz" de todas las existentes. Este método lo que hace es comparar el estado interno de dos objetos para ver si son iguales, mientras que `==` lo que hace es comparar las referencias.

En el caso de las cadenas, lo que hace es comparar que las dos matrices de caracteres que contienen los objetos `String` que comparamos sean iguales, que es realmente lo que nos interesa por regla general.

Así, para comparar dos cadenas cualquiera con seguridad debes usar este método:

```
String s1 = new String("campusMVP");
String s2 = new String("campusMVP");
System.out.println(s1.equals(s2)); //Devuelve true
System.out.println(s1.equals("campusMVP")); //Devuelve true
System.out.println("campusMVP".equals(s1)); //Devuelve true

```

Pero es que **da igual cómo obtengamos la cadena final** : siempre devuelve `true` si las cadenas que representan los dos objetos son iguales, por ejemplo:

```
String s1 = new String("campusMVP");
String s2 = new String("CAMPUSMVP");
System.out.println(s1.equals("campus" + "MVP")); //Devuelve true
System.out.println(s1.toLowerCase().equals(
            s2.toLowerCase()
        )); //Devuelve true

```

En este caso sí que podemos comparar las dos cadenas en minúsculas para ver si son iguales.

> **Conclusión 2** : las comparaciones de cadenas en Java hazlas siempre con `equals()`.

## "Internalizando" objetos de tipo cadena

Esto es ya casi una curiosidad más que otra cosa. Pero es que, aunque no es algo que se use muy habitualmente, existe la posibilidad de hacer que los objetos de tipo `String` que tengamos se "internalicen" de modo que pasen a formar parte del _pool_ de cadenas de texto, como si los hubiésemos declarado como literales.

Esto se consigue gracias al **método `intern()`** que tienen las cadenas. Lo que hace este método es lo mismo que ocurre cuando se declara una cadena literal: va al repositorio de cadenas y mira si ya existe la misma cadena ahí (en caso negativo la crea), y devuelve como resultado una referencia a la cadena preexistente, en lugar de crear una nueva.

Si llamamos a `intern()`, así, por ejemplo esto:

```
String s1 = new String("campusMVP");
String s2 = new String("campusMVP");
System.out.println(s1.intern() == s2.intern()); //Devuelve true

```

Devolverá `true` ya que `intern()` devuelve como resultado de la llamada la misma referencia a la misma cadena del _pool_ de cadenas.

Es decir, `intern()` lo que hace es desligar el objeto de los datos originales y asignarle el mismo dato que hay en el repositorio de cadenas. Visualmente sería algo así (fíjate en la cadena `s3`):

![Dibujo que muestra cómo una variable se desasigna de la ubicación en memoria original y se apunta a la misma cadena en el repositorio de cadenas](https://www.campusmvp.es/recursos/image.axd?picture=/2019/4T/java-cadenas-intern.png)

**¿Para qué vale esto de `intern()`?** Bueno, como decía, realmente pocas veces lo vas a utilizar, pero esto podría ayudarte a ahorrar mucha memoria en los casos en los que vas a generar muchas cadenas y un gran número de las cuales van a ser iguales.

Por ejemplo, imagínate que vas a obtener desde una base de datos muchos miles de registros de pedidos de clientes, y que tienes un número bastante limitado de clientes y de productos, lo que pasa que hacen muchos pedidos de lo mismo. "Internalizando" las cadenas evitarías tener los mismos nombres de cliente y de pedidos duplicados miles de veces en memoria, dejando tan solo unas pocas copias de los mismos dentro del repositorio de cadenas y ahorrando potencialmente mucha memoria, sobre todo si los nombres son largos.

Como **nota técnica adicional** sobre esto decir que, hasta Java 7, ya hace muchos años (se lanzó en 2011), el espacio disponible para almacenar las cadenas en el _pool_ era limitado y además no entraba dentro de la [recolección de basura](https://www.campusmvp.es/recursos/post/Modelos-de-gestion-de-memoria-II-Garbage-collector.aspx). Eso significaba que si abusabas mucho del "interning" podrías acabar con una excepción de tipo `OutOfMemory`, lo cual podría provocar un desastre en tu aplicación. Es por esto que en muchos artículos que encontrarás por ahí sobre este tema, y que están anticuados, te recomiendan no usar `intern()`. La realidad es que, a partir de Java 7, y por lo tanto en la práctica totalidad de las aplicaciones Java que vas a encontrar por ahí, **el repositorio de cadenas se guarda ya en [el "montón" (o _heap_)](https://www.campusmvp.es/recursos/post/Que-son-los-tipos-por-valor-y-por-referencia-en-NET-y-C.aspx)** (es decir, en memoria dinámica), por lo que no hay problemas de límites y además puede ser reclamada por el recolector de basura. Además en Java 13 (de septiembre de 2019) se permite también [la devolución al sistema operativo de memoria dinámica no utilizada](https://dev.to/campusmvp_es/java-13-ya-disponible-2i0o-temp-slug-8150137), con lo que incluso está más optimizado.

## En resumen

Las cadenas de texto son "bichos raros" en casi todos los lenguajes, pero en Java especialmente, al poder actuar a la vez como literales y como objetos, pero al mismo tiempo presentan diferencias a la hora de utilizarlas y en especial de compararlas.

Por ello las comparaciones de cadenas deben evitar el uso del operador `==` (que compara referencias de objetos y no sus datos) y emplear en cambio el método `equals()`. Además es importante conocer el funcionamiento interno de las cadenas, las implicaciones que tiene y cómo podemos optimizar el uso de memoria cuando manejamos cadenas de gran tamaño (o muchas) con valores idénticos.

Te de dejado [un pequeño patio de juegos](https://repl.it/@jmalarcon/Comparacion-de-cadenas) para que puedas comprobar el funcionamiento de todo lo explicado y probar a realizar cambios y ver cómo funcionan.

¡Espero que te resulte útil!