String y StringBuilder

Código 1 - substring()
String teléfonoConFormato(String teléfono) {
    var primerosTres = teléfono.substring(0, 3);
    var siguientesTres = teléfono.substring(3, 6);
    var últimos = teléfono.substring(6);
    var conFormato = String.format("(%s) %s-%s", primerosTres, siguientesTres, últimos);
    return conFormato;
}
Input
> teléfonoConFormato("1234567890")
Output
(123) 456-7890

El primer tipo de dato que usamos fue String. Ahora vamos a profundizar en él.

Las Strings están presentes en casi todo lo que hacemos en programación, ¡se podría decir que están sobre utilizados! Como recordatorio, un String es una secuencia de caracteres: letras, dígitos, signos de puntuación, emojis, etc.

Un String no es un tipo de dato primitivo, es una clase. Más adelante profundizaremos en qué es una clase, y cómo crear clases nuestras. Una diferencia fundamental entre datos primitivos y clases es que estás últimas pueden definir métodos que se llaman de una manera diferente de la que hemos usado hasta ahora.

En la línea 2, llamamos el método substring() de la variable <telefono>. Lo importante es que a diferencia de métodos como <Math.sqrt> o <String.format>, aquí el método se llama sobre una variable. Podemos pensarlo como que cada String creado, tiene un comportamiento que se accede mediante sus métodos.

Dado que un String es una cadena de caracteres, internamente se le asigna un índice a cada carácter para poder hacer referencia a ellos individualmente. Pero tiene algo extraño y es que el índice empieza en 0 y no en 1 como quizás uno esperaría.

La cadena "Hola Mundo!" que tiene 11 caracteres, tiene índices que empiezan en 0 y el último es el total de caracteres, su tamaño, menos 1. El caracter en el índice 0 es 'H', el caracter en el índice 4 es ' ' y en el índice 5 es 'M'. El índice del caracter 'a' es 3, mientras que el primer índice donde se encuentra una 'o' es 1 y el último es 9.

Uno de los aspectos más importantes con un String es poder extraer información de él y substring() es una de las formas más comunes de hacerlo. Como su nombre lo indica, devuelve solo una parte de un String, utilizando índices para indicar donde se hacen los cortes.

En la línea 02, teléfono.substring(0, 3) retornaría un String que consiste únicamente de los caracteres 0, 1 y 2. El último índice de substring(), es decir el segundo parámetro que toma, no está incluído en el String resultante.

Los parametros son de tipo int lo que quiere decir que puedes usar índices negativos, pero esto siempre producirá un error en tiempo de ejecución. Igualmente si usas un índice más grande que el máximo índice válido recibirás un error en tiempo de ejecución.

Pero la clase String tiene 2 métodos con el mismo nombre: subtring(), uno de ellos toma un int, y el otro toma dos. Esto, Que 2 métodos se llamen igual pero tengan diferentes parámetros, se llama sobrecarga de métodos. Por ahora lo importante es entender qué diferencia hay entre ambos. La versión que solo toma 1, asume que el índice final es el final de la cadena.

Estas dos expresiones son equivalentes:

teléfono.substring(6); // todos los dígitos después del índice 6

Y

// todos los dígitos entre 6 y
// el índice que es 1 menor que la longitud del String
teléfono.substring(6, phone.length() - 1);

Como habrás visto, existe un método String para calcular su longitud, llamado apropiadamente length(), que resulta útil si deseas encontrar el índice del último carácter, ya que solo necesitas restar 1 a la longitud.

Esta no es una lista exhaustiva de los métodos de String, pero te brindará suficientes herramientas para poder descubrir cómo usar una gran cantidad de métodos que encontrarás en la documentación.

Métodos Destacados de String

Otros métodos que podrías utilizar con frecuencia:

Input
> "Hola".charAt(0)
> "Hola".chatAt(3)
Output
'H'
'a'                

Input
> "casa".contains("as")
> "casa".contains("Ca")
> "casa".contains("sas")
> "casa".contains("me")
Output
true
False
False
False                

Input
> "hola".equals("hola")
> "hola".equals("adios")
> !("hola".equals("adios"))
Output
true
False
true

Input
> "hola".indexOf('h')
> "hola".indexOf('a')
> "hola".indexOf('z')
> "hola".indexOf('H')
Output
0
3
-1
-1

Input
> "casa".indexOf('a')
> "casa".indexOf('a', 2)
Output
1
3

Input
> "casa".lastIndexOf('a')
> "casa".lastIndexOf('a', 2)
Output
3
1

Métodos relacionados con Strings en otras clases:

Input
> Integer.parseInt("56")
Output
56
Código 2 - Parsear un String para extraer tiempos
// Un String de tiempo en formato ISO 8601
// p.j. 2021-11-20T13:15:00 pero también 08:05:00
 int tiempoEnSegundos(String fecha) {
    var separador = fecha.indexOf('T'); // puede ser -1
    var tiempo = (separador != -1) ? fecha.substring(separador + 1) : fecha;
    var horas = Integer.parseInt(tiempo.substring(0, 2));
    var minutos = Integer.parseInt(tiempo.substring(3, 5));
    var segundos = Integer.parseInt(tiempo.substring(6));
    var SEGUNDOS_POR_MINUTO = 60;
    var MINUTOS_POR_HORA = 60;
    var SEGUNDOS_POR_HORA = SEGUNDOS_POR_MINUTO * MINUTOS_POR_HORA;
    return SEGUNDOS_POR_HORA * horas + SEGUNDOS_POR_MINUTO * minutos + segundos;
}
Input
> tiempoEnSegundos("2023-04-20T00:00:15")
> tiempoEnSegundos("2023-04-20T00:01:00")
> tiempoEnSegundos("2023-04-20T00:10:00")
> tiempoEnSegundos("2023-04-20T01:00:00")
> tiempoEnSegundos("00:00:32")
> tiempoEnSegundos("00:02:00")
> tiempoEnSegundos("10:00:00")
Output
15
60
600
3600
32
120
36000

En la línea 04 utilizamos el método indexOf() para encontrar el índice en el que se encuentra el caracter 'T'. El estándar ISO 8601 indica que un tiempo que incluye una fecha y un tiempo, utiliza ese caracter para separar los datos de la fecha, de los del tiempo. Pero si el String solo indica un tiempo entonces no hay 'T', así que nuestro primer paso es garantizar que el String no incluya la parte de la fecha.

El método indexOf() retorna el menor índice en el que se encuentra un caracter dado. El estándar es claro que si hay un 'T', solo hay uno así que buscar el primero, equivale a buscar el único.

La primera acción que queremos hacer es quitar la información de la fecha si es que existe. Utilizamos el operador ternario para crear una nueva variable «tiempo» que haga un substring() si existe el caracter 'T', y en caso contrario sea una copia de la variable «fecha»

Ya garantizado que la variable «tiempo» no incluye información sobre una fecha, entonces queremos extraer los números que se encuentran entre caracteres ':', utilizando nuevamente substring(), pero usando el valor de retorno de estos como argumento de Integer.parseInt(). Lo importante de esta operación es que nos va a permitir multiplicar y sumar ese valor.

Finalmente sabemos que hay 60 minutos en una hora y 60 segundos en un minuto, por lo tanto podemos hacer una fórmula para terminar de encontrar cuántos segundos desde el inicio del día han transcurrido hasta la hora consignada en el String.

String Format

Crear un String a partir de cierta información es una tarea muy común en programación. Como vimos anteriormente, String.format() es una forma práctica de hacerlo. Hay muchas cosas sobre ese método, que bien vale la pena que conozcas. Pero vale la pena mostrar algunos ejemplos.

String.format() no solo funciona con Strings, funciona con otros tipos de datos:

Input
> // %d para ints 
  String.format("%d", 123);
> // %f para doubles 
  String.format("%f", 3.14);    
Output
"123"

"3.140000"    

Incluso podemos hacer conversión de números enteros a base 16:

Input
> // %x para dígitos en minúsculas
  String.format("%x", 123);
> // %X para dígitos en mayúsculas
  String.format("%X", 123);    
Output
"7b"

"7B"    

También podemos convertir Strings a mayúsculas:

Input
> // %s no modifica el String
  String.format("%s", "Código");
> // %S transforma a mayúsculas
  String.format("%S", "Código");   
Output
"Código"

"CÓDIGO"    

También podemos garantizar que el String resultante tenga un tamaño mínimo, utilizando un número entero entre el % y el indicador del tipo de dato:

Input
> String.format("%s", "Casa");
> String.format("%8s", "Casa");
> String.format("%d", 6789);
> String.format("%6d", 6789);  
Output
"Casa"
" Casa"
"6789"    
" 6789"

Para los números, puede indicar que si hacen falta caracteres para llegar a la longitud mínima, se usen cero en vez de espacios:

Input
> String.format("%06d", 6789);
Output
"006789"

Por último para números reales, podemos indicar cuántos número decimales queremos en el String resultante, utilizando un número antecedido por un punto. El resultado va a ser redondeado:

Input
> String.format("%f", Math.sqrt(2.0));
> String.format("%.3f", Math.sqrt(2.0));
Output
"1.414214"
"1.414"                

Todas estas reglas pueden ser combinadas. Y recuerda que hay aún otras funcionales adicionales, aunque estás son posiblemente las que más puedas usar.

StringBuilder

Código 3 - StringBuilder
String phoneFormatter(String teléfono) {
    var primerosTres = teléfono.substring(0, 3);
    var siguientesTres = teléfono.substring(3, 6);
    var últimos = teléfono.substring(6);
    var builder = new StringBuilder();
    builder.append("(");
    builder.append(primerosTres);
    builder.append(") ");
    builder.append(siguientesTres);
    builder.append(" - ");
    builder.append(últimos);
    var formattedPhone = builder.toString();
    return formattedPhone;
}

El código 3 muestra una implementación alternativa de phoneFormatter utilizando StringBuilder. Puede que esta no sea la mejor implementación, pero nos permite hablar de un tipo de dato importante: String Builder.

String.format() es una de las formas de construir Strings, ¡pero no es la única! String.format() es muy útil cuando tienes un patrón para String y quieres interpolar algunas variables. Pero tal vez quieras construir un String paso a paso, y eso es lo que StringBuilder hace mejor.

La línea 05 es cómo se crea un StringBuilder vacío. Los detalles de qué hace la palabra reservada «new», se discutirán más adelante, por ahora recuerda que lo necesitas para crear este tipo de dato. Con un StringBuilder puedes invocar el método append() tantas veces como quieras y usar cualquier tipo de variable que tengas para crear el String, paso a paso.

La línea 06 agrega el literal String “(“. Luego la línea 07 agrega la variable «primerosTres», que es un String. Pero no importaría si fuera de otro tipo, ya que append() puede aceptar cualquier tipo. Luego las siguientes 4 líneas agregan la información restante necesaria para crear la String. Después de agregar todos los datos necesarios, , llamamos al método toString() para convertir StringBuilder en un String en la línea 12, y finalmente devolver ese String resultante en la línea 13.

StringBuilder tiene más métodos para ayudarle a construir String pero no son importantes por ahora, append() que es el método más utilizado.

Misión secundaria: Inmutabilidad

Código 4 - StringBuilder
String stringEsInmutable(String s) {
    s.substring(1);
    return s;
} 
StringBuilder stringBuilderEsMutable
(StringBuilder builder) {
    builder.substring(1);
    return builder;
}
Input
> > stringsAreImmutable("foo")
> stringBuilderAreMutable(new StringBuilder("foo")
Output
foo
oo

Presentar StringBuilder nos ayuda a hablar sobre un gran tema en programación: la inmutabilidad. En Java, las strings son inmutables, mientras que los StringBuilders no lo son. Los tipos inmutables una vez creados no se pueden cambiar, sin importar qué métodos los invoques. Para tipos mutables, al invocar ciertos métodos vamos cambiando el valor del objeto.

En stringEsInmutable() invocamos el método substring() en la variable e inmediatamente retornamos esa variable. Como las strings son inmutables, substring() no cambia la string, crea una nueva string con el valor actualizado. Recuerda que el método substring(), puede tomar uno o dos argumentos. Con un argumento, devolverá el substring() desde el índice indicado hasta el final de la cadena por lo tanto s.substring(1) devuelve el substring que comienza desde el segundo carácter.

En stringBuilderEsMutable() también llamamos a substring() pero esta vez es el método substring() de StringBuilder. Como este tipo de dato es mutable, el valor de StringBuilder cambia después de ese llamado. Inmediatamente lo retornamos y así podemos ver el cambio.

Otra manera de verlo es que en las clases inmutables, debemos atrapar los valores de retorno para ver los cambios, de otra manera se pierden. Eso es lo que pasa con stringEsInmutable(), al llamar a substring(int) de String ese valor no lo atrapamos y el cambio no es visible de ninguna manera. En el caso de stringBuilderEsMutable(), llamamos el método substring(int) de StringBuilder y ese cambio se ve reflejado sin hacer nada adicional.

La inmutabilidad es un tema importante cuando creas tus propios tipos. Cuando utilices tipos ya definidos, simplemente lee la documentación o estudia cómo funcionan sus métodos para saber si es mutable o no.

Resumen

¡Los Strings son muy importantes en la programación! Es importante saber cómo crearlos y manipularlos. Para crearlos, la forma recomendada es String.format() y StringBuilder. Se prefiere lo primero si se puede crear la string en un solo paso, utilizando los datos disponibles y proporcionando una plantilla. Se prefiere lo último si crea la string en pasos, cómo en un bucle que verá en el siguiente capítulo.

Hay muchos métodos en la API de Strings, probablemente los más utilizados son: charAt, contains, compareTo, equals, indexOf y lastIndexOf.

Debido a que las strings a veces contienen información confidencial, son inmutables: ¡eso significa que una vez que se crea una string no puedes modificarla! Entonces, los métodos que parecen hacerlo, en realidad, devuelven una nueva string con los estados actualizados. Es muy importante que se sienta cómodo manejando tipos inmutables antes de profundizar en un capítulo posterior.

Strings

Quiz Interactivo


Antes de intentar los ejercicios, toma unos minutos para verificar tu entendimiento de Strings tomando este quiz.

  • Las preguntas y el orden de las opciones son diferentes cada vez que tomes el quiz.
  • Las preguntas que tengas mal, vuelven a aparecer luego hasta que las contestes correctamente.
  • Cada pregunta tiene una pista y una explicación.

Ejercicios

Errores del compilador y del tiempo de ejecución

Escribe los siguientes códigos y lee el error devuelto por el compilador o el entorno de ejecución. Luego escribe el código correcto.

E4.1
boolean empiezaConMinúscula(String s) {
   var primerCaracter = String.charAt(s, 0);
   return Character.isLowerCase(primerCaracter);
}
Input
> empiezaConMinúscula("manzana")
> empiezaConMinúscula("Manzana")    
Output
true
false                
E4.2
String pluralizar(int conteo, String s) {
   return (conteo < 2) ? s : s.format("%d %ss", conteo);
}
Input
> pluralizar(1, "código")
> pluralizar(2, "código")    
Output
"código"
"2 códigos"                
E4.3
boolean esVocal(char letra) {
    var letraMinúscula = Character.toLowerCase(letra);
    return 'aeiou'.contains(Character.toString(letraMinúscula));
}
Input
> esVocal('a')
> esVocal('A')
> esVocal('c')
> esVocal('@')   
Output
true
true
false
false

Ejercicios de codificación

E4.4

Escribe una función que tome un String y lo devuelva con la primera letra en mayúscula y las demás en minúsculas (en inglés esto se llama title case).

E4.4
String titleCase(String texto) {
    var primeraLetra = texto.substring(___, ___);
    var restoDelTexto = texto.substring(___);
    return String.format("%S%s", primeraLetra, restoDelTexto.toLowerCase());
}
Input
> titleCase("java")
> titleCase("Java")
> titleCase("JAVA")
Output
"Java"
"Java"
"Java"

E4.5

Escribe una función terminaEnS() que verifique si una palabra termina en la letra S.

E4.5
boolean terminaEnS(String texto) {
    var últimoCaracter = texto.___(texto.___ - 1);
    return (últimoCaracter == 's') || (últimoCaracter == 'S');
}
Input
> terminaEnS("miércoles")
> terminaEnS("jueves")
> terminaEnS("sábado")
Output
true
true
false

E4.6

Es útil cuando se intenta escribir una función, codificar primero una versión más simple de la misma y extenderla desde allí. Escribe una función que verifique si un String de dos letras es un palíndromo. Un palíndromo es una oración que se lee igual independientemente de dónde se lea, p.j. “Anita, lava la tina".

Input
> esPalindrome("ab")
> esPalindrome("ee")
> esPalindrome("Aa")
Output
false
true
true

E4.7

Escribe una función esExterno() que tome una string que represente un enlace y devuelva true si el enlace es externo. Para este ejercicio tenga en cuenta que los enlaces internos comienzan con “/” y los enlaces externos utilizan los protocolos “http” o “https”.

Input
> esExterno("/definetely-internal")
> esExterno("https://www.java.com")
> esExterno("http://dont-go.com")
Output
false
true
true

E4.8

Escribe una función extensión() que tome un String que represente una ruta a un archivo y devuelva la extensión del archivo, todo lo que viene después del último punto del archivo que es parte del nombre del archivo. Las rutas pueden ser relativas o absolutas. Recuerda que es posible que un archivo no tenga extensiones.

Input
> extension("debug.sh")
> extension("/path/to/debug.sh")
> extension("/the.dot/file.java")
> extension("no-extension")
Output
"sh"
"sh"
"java"
""

E4.9

El análisis de Strings es muy común en la programación y generalmente existen algunos tipos o bibliotecas que ayudan con eso. Pero a veces solo necesitas una operación muy específica, por lo que puede tener sentido escribir una función básica en lugar de agregar una dependencia adicional. Escribe una función que tome una URL y devuelva el dominio, la parte que viene después del protocolo y termina en la siguiente barra o al final de la string si no hay ninguna. Puede suponer que todos los parámetros comenzarán con "https://", que es el protocolo.

Input
> domain("https://www.java.com/en/")
> domain("https://jcp.org")
Output
"www.java.com"
"jcp.org"

E4.10

¿Te has preguntado cómo se calcula la Semana Santa? El domingo de Pascua es el primer domingo después de la primera luna llena después del equinoccio de primavera. Dado que las lunas llenas se rigen por un ciclo distinto de los meses, cada año esta fecha cambia. Resulta que hay un algoritmo que puede ayudar a tener en cuenta todos estos sucesos y es el que se utiliza hoy en día para determinar esa fecha. El algoritmo, dado el año, es el siguiente:

var a = año % 19;
var b = año / 100;
var c = año % 100;
var d = b / 4;
var e = b % 4;
var g = ((8 * b) + 13) / 25;
var h = ((19 * a) + b - d - g + 15) % 30;
var i = c / 4;
var k = c % 4;
var l = (32 + (2 * e) + (2 * i) - h - k) % 7;
var m = (a + (11 * h) + (19 * l)) / 433;
// n es el número del mes, 3 = marzo, 4 = abril
var n = (h + l - (7 * m) + 90) / 25;
// p es el día del mes
var p = (h + l - (7 * m) + (33 * n) + 19) % 32;
        

Por ejemplo, si el año fuera 2024, entonces n = 3, p = 31

Escribe una función domingoDePascua(int) que dado un parámetro entero, el año a averiguar, retorne la fecha del domingo de pascua para ese año como un String con el formato "YYYY/mm/dd" es decir el año con sus 4 dígitos, el mes con 2 dígitos "03" para marzo y "04" para abril y el día también con 2 dígitos, si el día es menor de 10 con un 0 por delante, p.j. "05", "21", etc.

Input
> domingoDePascua(2024)
> domingoDePascua(2025)
> domingoDePascua(1961)
Output
"2024/03/31"
"2025/04/20"
"1961/04/02"

E4.11

Hay muchos métodos para representar colores en un computador. Uno de ellos es usando el modelo de color RGB en que los colores se describen utilizando diferentes intensidades de 3 colores: Rojo, Verde (Green en inglés) y Azul (Blue en inglés) Esta intensidad es un valor entre 0 y 255.

Aunque uno podría representar un color RGB como un conjunto de 3 números entre 0 y 255, la manera más común de representarlos es con un String de 6 dígitos, donde los primeros 2 representan la intensidad del rojo, los siguientes 2 la intensidad del verde y los últimos 2 la intensidad del azul pero escritos en base hexadecimal o base 16 (la razón siendo que todos los números entre 0 y 255 se pueden escribir máximo con 2 dígitos)

Escribe una función hexString(int, int, int) que reciba 3 números enteros, en el rango de 0 a 255, y retorne un String con todos los caracteres en mayúsculas representando el número RGB como un String.

Input
> hexString(0, 0, 0)
> hexString(255, 255, 255)
> hexString(12, 15, 77)
Output
"000000"
"FFFFFF"
"0C0F4D"

E4.12

Escribe una función correoElectronicoValido() que, dado un correo electrónico, devuelva true si es un correo electrónico válido. A los efectos del ejercicio un correo electrónico válido tiene estas características:

  • Tiene solo una @
  • Tiene solo un "." después de la @
  • Hay al menos un carácter antes de @
  • Hay al menos un carácter entre @ y el último "."
  • Hay al menos un carácter después del último "."

Input
> correoElectronicoValido("abc@me.com")
> correoElectronicoValido("abc@me.")
> correoElectronicoValido("abc@mecom")
> correoElectronicoValido("abc@me@com")
> correoElectronicoValido("@me.com")
> correoElectronicoValido("a.bc@me.com")
> correoElectronicoValido("abc@me.co.m")
> correoElectronicoValido("a@c@me.com")
Output
true
false
false
false
false
true
false
false

E4.13

En sitios web donde se muestra mucho texto en combinación con otros elementos, es usual tener que cortar un título por temas de espacio. Pero si cortamos un título, es importante que podamos transmitir al lector que está leyendo un título acortado y usualmente se hace esto agregando puntos suspensivos. A esta operación de acortar un título y agregar puntos suspensivos si el título fue acortado, lo llamaremos elipsificar (porque en inglés el caracter de los puntos suspensivos se llama ellipsis)

Escribe una función para elipsificar un String. Así es como debería funcionar:

  • Los puntos suspensivos solo se agregan si el texto se debe acortar y es el último carácter del String en caso de estar presentes.
  • Los puntos suspensivos solo se agregan en un lugar donde antes había un espacio y no debe haber espacios inmediatamente antes del carácter.
  • La cadena debe ser la más larga posible teniendo mínimo la longitud deseada más el carácter de los puntos suspensivos.

Input
> elipsificar("Java is fun", 4)
> elipsificar("Java is fun", 6)
> elipsificar("Java is fun", 11)
> elipsificar("Java is fun", 20)
Output
"Java…"
"Java is…"
"Java is fun"
"Java is fun"

Para todos los ejemplos, ten en cuenta que "Java is fun" es un String de 11 caracteres. El primer ejemplo busca acortar el título para que tenga solo 4 caracteres. El primer espacio después del cuarto caracter es el que se encuentra después de "Java" que tiene una longitud menor que el original, por lo tanto el String retornado es "Java…". Para el segundo ejemplo, la longitud máxima es 6, pero no podemos partir ahí mismo porque partiríamos la palabra "is", el primer espacio después de ese espacio es precisamente al final de la palabra "is". El String retornado es "Java is…". Para los últimos 2 ejemplos, la cadena cuenta ya con menos o la misma cantidad de caracteres necesaria así que retornamos la cadena sin ningún cambio.

© 2022 Carlos Obregón. This work is licensed under Creative Commons Attribution 4.0 International (CC BY 4.0)

Scroll to Top