Un String es un objeto inmutable que representa una secuencia de caracteres.
Si vienes de C o C++, probablemente recuerdes las dificultades de gestionar arrays de caracteres (char*), punteros y el terminador nulo \0.
En Java, decidieron que el texto era tan importante que merecía un tratamiento VIP. Por eso crearon la clase java.lang.String.
A simple vista parece un tipo primitivo más. Lo usas con comillas dobles "Hola" y te olvidas. Pero bajo el capó, String es un Objeto con un comportamiento único y fascinante diseñado para optimizar la memoria al máximo.
Hoy vamos a ver las tripas del texto en Java y a evitar la trampa en la que caen todos los principiantes.
¿Qué es realmente un String?
En Java, un String es una secuencia de caracteres Unicode. Pero, técnicamente, es un Objeto.
Sin embargo, Java le da un trato especial (azúcar sintáctico) para que sea fácil de usar:
// Forma "Literal" (La recomendada)
String saludo = "Hola Mundo";
// Forma "Objeto" (Técnicamente válida, pero NO la uses)
String saludoFeo = new String("Hola Mundo");
¿Por qué existen dos formas? Para entender la diferencia, primero necesitamos conocer la propiedad clave de los strings.
La propiedad clave: inmutabilidad
Los objetos String en Java son inmutables.
Esto significa que, una vez que se crea un String en memoria, jamás se puede modificar. No se puede cambiar ni una letra.
“¡Pero Luis!,” me dirás, “¡Yo me paso el día modificando Strings!”
String texto = "Hola";
texto = texto + " Mundo"; // ¿No acabo de modificarlo?
No. Lo que ha pasado en la memoria es esto:
Java crea el objeto "Hola".
Java crea el objeto " Mundo".
Java crea un TERCER objeto nuevo "Hola Mundo".
Tu variable texto ahora apunta al nuevo objeto.
El antiguo "Hola" se queda “huerfano” en memoria hasta que el Garbage Collector lo borra.
Impacto en Rendimiento:
Si haces esto dentro de un bucle de 1.000 iteraciones, estarás creando y tirando a la basura 1.000 objetos intermedios. Para modificar texto de forma eficiente, usaremos StringBuilder (lo veremos en el próximo artículo).
¿Por qué hacerlos inmutables?
Parece ineficiente, pero tiene ventajas enormes:
- Seguridad: Como no cambian, son seguros para guardar contraseñas, URLs o usar en entornos multihilo.
- HashCode Caching: Como no cambia, su código hash se calcula una vez y se guarda. Esto hace que los
HashMapcon claves String sean rapidísimos. - El String Pool: La gran optimización.
El pool de cadenas de String
Como los Strings son inmutables y se usan muchísimo, Java tiene una zona especial en la memoria Heap llamada String Pool.
Cuando creas un String usando comillas String s = "Hola";:
- Java mira en la piscina: “¿Existe ya el texto ‘Hola’?”
- Si existe: No crea nada. Simplemente te devuelve una referencia al objeto que ya existía. Reutiliza la memoria.
- Si no existe: Crea el objeto en la piscina y te da la referencia.
Veamos el impacto de esto:
String s1 = "Java";
String s2 = "Java"; // Java detecta que es igual y REUTILIZA la referencia de s1.
// ¡Son el MISMO objeto físico en memoria!
System.out.println(s1 == s2); // true
La excepción: new String()
Si usas la palabra clave new, estás forzando a Java a crear un objeto nuevo en el Heap normal, saltándose el chequeo del Pool.
String s3 = new String("Java"); // Crea un objeto nuevo, duplicado e inútil.
System.out.println(s1 == s3); // false (Son objetos distintos con el mismo texto)
Consejo: Nunca uses new String("texto") a menos que tengas una razón muy oscura y específica. Usa siempre literales.
La trampa mortal: == vs .equals()
Aquí es donde el 99% de los estudiantes suspenden el primer examen.
==: Compara referencias de memoria (¿Son el mismo objeto?)..equals(): Compara contenido (¿Tienen las mismas letras?).
Debido al String Pool, a veces == devuelve true (como vimos con s1 y s2), lo que te da una falsa sensación de seguridad. Pero en el mundo real, los Strings vienen de la entrada del usuario, de bases de datos o de cálculos, y ahí no se garantiza el Pool.
String a = "Hola";
String b = "Hola";
String c = new Scanner(System.in).next(); // Usuario escribe "Hola"
System.out.println(a == b); // true (Gracias al Pool)
System.out.println(a == c); // FALSE (¡Peligro!)
System.out.println(a.equals(c)); // true (compara el contenido)
Para comparar el contenido de dos strings, usa .equals(). El operador == sigue siendo útil cuando quieres comprobar si ambas variables contienen la misma referencia.