Clase String



Como vimos al tratar sobre los tipos básicos en Kotlin, el tipo String hace referencia a un tipo de dato inmutable formado por una sucesión de caracteres a los que se puede acceder por su índice. En esta entrada vamos a desarrollar y ampliar algunos conceptos sobre este tipo de dato y veremos diversas maneras de trabajar con ellos.

La clase String representa una cadena o secuencia de caracteres, cuya instancia es creada entre comillas dobles y se conocen como strings literales. La propiedad length de la clase String devuelve un número entero que indica la longitud o número de caracteres de una secuencia (incluyendo espacios):
val str = "Hola, soy un String."
println(str)
println("str tiene " + str.length + " caracteres")

Un String está compuesto por caracteres a los que se puede acceder por su índice (empezando desde el cero):
println(str[3]) // a

Combinando la propiedad length con los índices podemos obtener el primer y último carácter de una secuencia:
val str = "Kotlin Doc"
println (str[0])            // K
println (str[str.length-1]) // c

También los podemos obtener con los métodos first() y last():
println(str.first()) // K
println(str.last())  // c

Un String es inmutable pero se puede reasignar otro valor si la variable es declarada mutable con var:
var str = "hola"
// str[0] = 'H' // ERROR
str = "Adiós"

Como sobre cualquier secuencia, podemos recorrer e iterar sobre los elementos (caracteres) de un String:
val str = "abcd"
for (c in str) {
    print(c) // abcd
}

Otro ejemplo para recorrer un String:
val frase = "Kotlin"
for (car in frase) {
    print("$car ")
}
println()
frase.forEach { c -> print(String.format("%#x ", c.toByte())) }

println()
frase.forEachIndexed { idx, e -> println("caracter $idx: $e ") }
K o t l i n
0x4b 0x6f 0x74 0x6c 0x69 0x6e
caracter 0: K
caracter 1: o
caracter 2: t
caracter 3: l
caracter 4: i
caracter 5: n

Kotlin tiene dos tipos especiales de strings literales: las cadenas de caracteres de escape y las cadenas en bruto o sin formato.

Los caracteres de escape se escriben anteponiendo una barra invertida y realizan una operación específica. Las secuencias de escape admitidas son: \t, \b, \n, \r, \', \", \\, \$. Algunos ejemplos:
println("Línea 1 \nLínea 2 \nLínea 3") // nueva línea
println("Uno\t\tDos\t\tTres")  // tabulaciones: Uno     Dos     Tres
println("Hola\bMundo")         // retorno: HolMundo
println("Hola\rMundo")         // retorno de carro: Mundo
println("Pepe dijo: \"Hola\"") // Pepe dijo: "Hola"
println("home\\usuario\\descargas") // home\usuario\descargas

Una cadena en bruto está delimitada por comillas triples y no puede contener caracteres de escape:
val texto = """
Ejemplo "for":
for (c in string)
    print(c)
"""
println(texto)
    Ejemplo "for":
    for (c in string)
        print(c)

En estos casos podemos eliminar los espacios en blanco con la función trimMargin() y añadiendo un prefijo a cada línea:
val texto = """
|Ejemplo "for":
|for (c in string)
|    print(c)
""".trimMargin()
println(texto)
Ejemplo "for":
for (c in string)
    print(c)

Por defecto se utiliza el carácter | como prefijo de margen, pero podemos elegir otro carácter o caracteres y pasarlos como parámetro de trimMargin:
val texto = """
>Ejemplo "for":
>for (c in string)
>    print(c)
""".trimMargin(">")
println(texto)

Otra opción es eliminar la sangría con trimIndent():
val soneto = """
    Un soneto me manda hacer Violante,
    y en mi vida me he visto en tal aprieto:
    Catorce versos dicen que es soneto:
    Burla burlando van los tres delante.
"""
println(soneto.trimIndent())

Podemos concatenar String usando el operador +. Esto también funciona para concatenar String con valores de otros tipos siempre que el primer elemento en la expresión sea un String:
val letras = "abc"
val masLetras = "def"
val abecedario = letras + masLetras
print(abecedario) // abcdef
println()
val letrasMasNum = "abc" + 1
println(letrasMasNum + masLetras) // abc1def

No obstante, en la mayoría de los casos es preferible utilizar plantillas de String antes que esta concatenación de String. Las plantillas de String son expresiones que se evalúan y cuyos resultados se interpolan dentro de la secuencia. Una expresión de plantilla se escribe siempre anteponiendo el signo de dólar ($) a una variable o a una expresión entre llaves:
val i = 10
println("i = $i") // i = 10

val nombre = "Juan"
val edad = 43
println("$nombre tiene $edad años.")

val letras = "abc"
val masLetras = "def"
println("$letras$masLetras") // abcdef

val s = "abc"
println("$s.length = ${s.length}") // abc.length = 3

Para comparar el contenido de dos String podemos usar el operador == o bien el método compareTo(). Este método compara dos objetos y devuelve cero si son iguales, un número negativo si el primer objeto es menor que el segundo, y un número positivo si el primero es mayor que el otro. Opcionalmente se puede usar con un segundo parámetro (ignoreCase) para omitir mayúsculas y minúsculas en la comparación:
val s1 = "Abc"
val s2 = "abc"

println(s1 == s2) // false

println(s1.compareTo(s2, false)) // -32
println(s1.compareTo(s2))   // -32 (por defecto es false y se puede omitir)

println("Ignorando mayúsculas")

if (s1.compareTo(s2, true) == 0) { // 0
    println("Secuencias iguales")
} else {
    println("Secuencias distintas")
}

Kotlin ofrece una amplia variedad de métodos para trabajar con String y realizar operaciones sobre ellos pero, ya que son inmutables, todas esas operaciones crean una nueva secuencia en lugar de modificar la secuencia original.

Kotlin tiene varios métodos para trabajar con la capitalización (mayúsculas y minúsculas) de una secuencia de caracteres:
val str = "homer Simpson"
println(str.capitalize())  // Homer Simpson
println(str.toUpperCase()) // HOMER SIMPSON
println(str.toLowerCase()) // homer simpson
println("Homer".decapitalize()) // homer

Kotlin distingue entre secuencias vacías y en blanco: una cadena vacía no tiene ningún carácter, mientras que una cadena en blanco contiene cualquier número de espacios en blanco, y existen métodos específicos para cada una:
val tab = "\t"
val esp = "  "
val vac = ""
println(tab.isEmpty()) // false
println(tab.isBlank()) // true
println(esp.isEmpty()) // false
println(esp.isBlank()) // true
println(vac.isEmpty()) // true
println(vac.isBlank()) // true

También existen métodos para quitar caracteres de espacio en blanco de una cadena:
val str = " Ab cd\t"
println("str tiene ${str.length} caracteres") // 7

val str1 = str.trimEnd() // elimina los espacios en blanco finales
println(str1) //  Ab cd
println("str1 tiene ${str1.length} caracteres") // 6 caracteres

val str2 = str.trimStart() // elimina los espacios en blanco iniciales
println(str2) // Ab cd
println("str2 tiene ${str2.length} caracteres") // 6 caracteres

val str3 = str.trim() // elimina todos los espacios en blanco iniciales y finales
println(str3) // Ab cd
println("str3 tiene ${str3.length} caracteres") // 5 caracteres

El método filter() devuelve una cadena que contiene solo aquellos caracteres de la cadena original que coinciden con el argumento dado:
// función de extensión que devuelve true o false
fun Char.esVocal(): Boolean {
    if (this == 'a' || this == 'e' || this == 'i' ||
        this == 'o' || this == 'u'
    ) return true
    return false
}

/* la misma función también se puede escribir así:
fun Char.esVocal(): Boolean =
    this == 'a' || this == 'e' || this == 'i' ||
    this == 'o' || this == 'u'
*/

/* aunque yo prefiero así:
fun Char.esVocal(): Boolean {
    when(this) {
        'a', 'e', 'i', 'o', 'u' -> return true
    }
    return false
}
*/

fun main() {
    val str = "La familia Simpson."
    val nVocales = str.filter { c -> c.toLowerCase().esVocal() }
    println("Hay ${nVocales.length} vocales: $nVocales") // Hay 7 vocales: aaiiaio
}

También podemos comprobar si una secuencia empieza o termina por uno o varios caracteres con los métodos startsWith() y endsWith(), que devuelven true en caso positivo:
fun main() {

    val palabras = listOf(
        "tanque", "chicos", "turista", "doce", "pelota", "tulipán", "casas",
        "coche", "quizá", "sombrero", "tonel", "átomos", "hormiga"
    )

    val palabrasTInicio = palabras.filter { e -> inicioT(e) }
    println(palabrasTInicio) // [tanque, turista, tulipán, tonel]

    val palabrasOsFinal = palabras.filter { e -> finalOs(e) }
    println(palabrasOsFinal) // [chicos, átomos]
}

fun inicioT(palabra: String) = palabra.startsWith("t")

fun finalOs(palabra: String) = palabra.endsWith("os")

El método replace() devuelve una nueva cadena obtenida al reemplazar todas las ocurrencias de una determinada secuencia en una cadena por otra secuencia, sin modificar la cadena original:
val str = "x por x es igual a cuatro."
val str2 = str.replace("x", "dos")
println(str2) // dos por dos es igual a cuatro.

El método toString() sirve para proporcionar una representación tipo String de un objeto:
class Ciudad(private var nombre: String, private var poblacion: Int) {
    override fun toString(): String {
        return "Población de $nombre: ${String.format("%,d", poblacion)}"
    }
}

fun main() {
    val ciudades = listOf(
        Ciudad("Madrid", 3_223_334),
        Ciudad("Barcelona", 1_620_343),
        Ciudad("Albacete", 173_050)
    )
    ciudades.forEach { e -> println(e) }
}
Población de Madrid: 3.223.334
Población de Barcelona: 1.620.343
Población de Albacete: 173.050

También hay métodos para rellenar secuencias con un carácter específico:
val nums = intArrayOf(657, 122, 3245, 345, 99, 18)
nums.toList().forEach { e -> println(e.toString().padStart(10, '.')) }
// parámetros de padStart: (longitud, caracter de relleno)
.......657
.......122
......3245
.......345
........99
........18

Por último, como hemos visto en un par de ejemplos en esta misma entrada, se puede dar formato de salida a un String utilizando String.format() con argumentos específicos utilizando el símbolo % seguido de un especificador según el tipo de datos a que se aplica (d: Int, s: String, b: Boolean, c: Character, f: Float, t: Date o Time...). En el siguiente código vamos a ver algunos ejemplos:
import java.util.Locale

fun main() {
    val nombre = "Juan"
    println(String.format("Mi nombre es %s", nombre)) // Mi nombre es Juan

    val miFormato = "Mi nombre es %s"
    println(miFormato.format(nombre)) // Mi nombre es Juan

    // lo mismo con una plantilla de String
    println("Mi nombre es $nombre") // Mi nombre es Juan

    // especificadores de cadena y número
    println(String.format("%s = %d", "Resultado", 35)) // Resultado = 35

    // formatea números largos con separador de miles
    println(String.format("%,d", 42125741)) // 42.125.741
    println(String.format(Locale.ENGLISH, "%,d", 42125741)) // 42,125,741
    println(String.format(Locale.GERMAN, "%,d", 42125741))  // 42.125.741

    // especifica el ancho
    println(String.format("|%10d|", 93))  // |        93|
    // ancho y justificación a la izquierda
    println(String.format("|%-10d|", 93)) // |93        |
    // ancho y relleno de ceros
    println(String.format("|%010d|", 93)) // |0000000093|

    // incluye el signo + delante de los números positivos (el signo - simepre aparece en los negativos)
    println(String.format("%+d", 93)) // +93
    // incluye un espacio solo delante de los números positivos
    println(String.format("|% d|", 93))  // | 93|
    println(String.format("|% d|", -93)) // |-93|
    // encierra los números negativos entre paréntesis y quita el signo -
    println(String.format("|%(d|", -36)) // |(36)|

    val pi = 3.14159265358979323
    println(String.format("%g", pi))   // 3,14159
    println(String.format("%.4g", pi)) // 3,142
    println(String.format("%.2f", pi)) // 3,14

    val decimal = 3.0
    println(String.format("%f", decimal)) // 3,000000

    // especifica longitud del campo
    println(String.format("|%20s|", "Hola Mundo"))   // |          Hola Mundo|
    // justificación a la izquierda
    println(String.format("|%-20s|", "Hola Mundo"))  // |Hola Mundo          |
    // especifica el número máximo de caracteres
    println(String.format("|%.4s|", "Hola Mundo"))   // |Hola|
    // ancho y número máximo de caracteres
    println(String.format("|%20.4s|", "Hola Mundo")) // |                Hola|
}

Comentarios

Entradas populares

I/O: entrada y salida de datos en consola

Recursos gratis para aprender Kotlin

Lectura y escritura de archivos