Cómo usar listas en Kotlin

1. Antes de comenzar

Hacer listas para todo tipo de situaciones de la vida cotidiana es algo habitual. Por ejemplo, hacemos listas de deseos, de compras, de cosas por hacer o de invitados a un evento. En la programación, las listas también resultan muy útiles. Por ejemplo, en una app, podría haber una lista de artículos de noticias, de canciones, de eventos del calendario o de publicaciones en redes sociales.

Aprender a crear y usar listas es un concepto de programación importante que puedes agregar a tu caja de herramientas y te permitirá crear aplicaciones más sofisticadas.

En este codelab, usarás el Playground de Kotlin para familiarizarte con las listas de Kotlin y crear un programa desde el cual se pidan diferentes variedades de sopa de fideos. ¿Tienes hambre?

Requisitos previos

  • Estar familiarizado con el uso del Playground de Kotlin a efectos de crear y editar programas de Kotlin
  • Conocer los conceptos básicos de programación de Kotlin de la unidad 1 del curso Aspectos básicos de Android en Kotlin: la función main(), los argumentos de las funciones y los valores que se mostrarán, las variables, los tipos de datos y las operaciones, así como las sentencias de flujo de control
  • Ser capaz de definir una clase de Kotlin, crear una instancia de objeto desde ella y acceder a sus propiedades y métodos
  • Saber cómo crear subclases y comprender cómo se heredan unas de otras

Qué aprenderás

  • Cómo crear y usar listas en Kotlin
  • Cuál es la diferencia entre la List y la MutableList, y cuándo usar cada una
  • Cómo iterar en todos los elementos de una lista y realizar una acción en cada uno

Qué compilarás

  • Experimentarás con listas y operaciones de lista en el Playground de Kotlin.
  • Crearás un programa de pedido de comida que usará listas en el Playground de Kotlin.
  • Tu programa podrá crear un pedido, agregarle fideos y vegetales, y calcular su costo total.

Requisitos

2. Introducción a las listas

En codelabs anteriores, aprendiste los tipos de datos básicos en Kotlin, como Int, Double, Boolean y String. Estos te permiten almacenar un determinado tipo de valor dentro de una variable. Pero ¿qué sucede si deseas almacenar más de un valor? En este caso, veremos que resulta muy útil tener un tipo de datos de List.

Una lista es una colección de elementos con un orden específico. Hay dos tipos de listas en Kotlin:

  • Lista de solo lectura: List no se puede modificar después de su creación.
  • Lista mutable: MutableList se puede modificar después de su creación, lo que significa que puedes agregar, quitar o actualizar sus elementos.

Cuando usas List o MutableList, debes especificar el tipo de elemento que pueden contener. Por ejemplo, List<Int> contiene una lista de números enteros y List<String> contiene una lista de cadenas. Si defines una clase Car en tu programa, puedes tener una List<Car> que contenga una lista de instancias de objetos Car.

La mejor manera de entender las listas es probarlas.

Crea una lista

  1. Abre el Playground de Kotlin y borra el código existente proporcionado.
  2. Agrega una función main() vacía. Todos los pasos de código siguientes se incluirán dentro de esta función main().
fun main() {

}
  1. En main(), crea una variable llamada numbers de tipo List<Int> porque contendrá una lista de solo lectura de números enteros. Crea una nueva clase List con la función de la biblioteca estándar de Kotlin listOf() y pasa los elementos de la lista como argumentos separados por comas. listOf(1, 2, 3, 4, 5, 6) muestra una lista de solo lectura de números enteros del 1 al 6.
val numbers: List<Int> = listOf(1, 2, 3, 4, 5, 6)
  1. Si el tipo de variable se puede adivinar (o inferir) en función del valor a la derecha del operador de asignación (=), puedes omitir el tipo de datos de la variable. Por lo tanto, puedes acortar esta línea de código de la siguiente manera:
val numbers = listOf(1, 2, 3, 4, 5, 6)
  1. Usa println() para imprimir la lista numbers.
println("List: $numbers")

Recuerda que colocar $ en la cadena significa que lo que sigue es una expresión que se evaluará y se agregará a esta cadena (consulta las plantillas de cadenas). Esta línea de código también se puede escribir como println("List: " + numbers)..

  1. Recupera el tamaño de una lista con la propiedad numbers.size e imprímela también.
println("Size: ${numbers.size}")
  1. Ejecuta el programa. El resultado es una lista con todos sus elementos y el tamaño de esta. Observa los corchetes [], que indican que esta es una List. Dentro de estos corchetes, se encuentran los elementos de numbers, separados por comas. Además, observa que los elementos están en el mismo orden en que los creaste.
List: [1, 2, 3, 4, 5, 6]
Size: 6

Accede a los elementos de la lista

La funcionalidad específica de las listas es que puedes acceder a cada elemento de ellas mediante su índice, que es un número entero que representa la posición. Este es un diagrama de la lista numbers que creamos, donde se muestra cada elemento y su correspondiente índice.

cb6924554804458d.png

En realidad, el índice es un desplazamiento del primer elemento. Por ejemplo, cuando indicas list[2], no solicitas el segundo elemento de la lista, sino el elemento que está desplazado 2 posiciones respecto del primer elemento. Por lo tanto, list[0] es el primer elemento (ningún desplazamiento), list[1] es el segundo (desplazamiento de 1), list[2] es el tercero (desplazamiento de 2), y así sucesivamente.

Agrega el siguiente código luego del existente en la función main(). Ejecuta el código después de cada paso de modo que puedas verificar si el resultado es el esperado.

  1. Muestra el primer elemento de la lista en el índice 0. Puedes llamar a la función get() con el índice deseado como numbers.get(0) o puedes usar la sintaxis abreviada con corchetes alrededor del índice como numbers[0].
println("First element: ${numbers[0]}")
  1. A continuación, muestra el segundo elemento de la lista en el índice 1.
println("Second element: ${numbers[1]}")

Los valores de índice válidos ("índices") de una lista van de 0 al último índice, que es el tamaño de la lista menos 1. Esto significa que, para tu lista numbers, los índices van de 0 a 5.

  1. Imprime el último elemento de la lista mediante numbers.size - 1 para calcular su índice, que debe ser 5. El acceso al elemento en el 5.º índice debe mostrar 6 como resultado.
println("Last index: ${numbers.size - 1}")
println("Last element: ${numbers[numbers.size - 1]}")
  1. Kotlin también admite las operaciones first() y last() en una lista. Llama a numbers.first() y numbers.last(), y observa el resultado.
println("First: ${numbers.first()}")
println("Last: ${numbers.last()}")

Notarás que numbers.first() muestra el primer elemento de la lista y numbers.last() muestra el último.

  1. Otra operación útil de lista es el método contains() a efectos de saber si un determinado elemento está en la lista. Por ejemplo, si tienes una lista de nombres de empleados de una empresa, puedes usar el método contains() para averiguar si un nombre específico está en la lista.

En tu lista numbers, llama al método contains() con uno de los números enteros presentes en la lista. numbers.contains(4) mostrará el valor true. Luego, llama al método contains() con un número entero que no esté en tu lista. numbers.contains(7) mostrará false.

println("Contains 4? ${numbers.contains(4)}")
println("Contains 7? ${numbers.contains(7)}")
  1. El código terminado debería verse de la siguiente manera: Los comentarios son opcionales.
fun main() {
    val numbers = listOf(1, 2, 3, 4, 5, 6)
    println("List: $numbers")
    println("Size: ${numbers.size}")

    // Access elements of the list
    println("First element: ${numbers[0]}")
    println("Second element: ${numbers[1]}")
    println("Last index: ${numbers.size - 1}")
    println("Last element: ${numbers[numbers.size - 1]}")
    println("First: ${numbers.first()}")
    println("Last: ${numbers.last()}")

    // Use the contains() method
    println("Contains 4? ${numbers.contains(4)}")
    println("Contains 7? ${numbers.contains(7)}")
}
  1. Ejecuta tu código. Este es el resultado.
List: [1, 2, 3, 4, 5, 6]
Size: 6
First element: 1
Second element: 2
Last index: 5
Last element: 6
First: 1
Last: 6
Contains 4? true
Contains 7? false

Las listas son de solo lectura

  1. Borra el código del Playground de Kotlin y reemplázalo por el código que aparece a continuación. La lista colors se inicializa en una lista de 3 colores representados como Strings.
fun main() {
    val colors = listOf("green", "orange", "blue")
}
  1. Recuerda que no puedes agregar ni modificar elementos en una List de solo lectura. Observa lo que sucede cuando agregas un elemento a la lista o lo modificas estableciéndolo en un valor nuevo.
colors.add("purple")
colors[0] = "yellow"
  1. Luego de ejecutar el código, se mostrarán varios mensajes de error. En esencia, los errores indican que el método add() no existe para List y que no se te permite cambiar el valor de un elemento.

dd21aaccdf3528c6.png

  1. Quita el código incorrecto.

Observaste de primera mano que no es posible cambiar una lista de solo lectura. Sin embargo, existen varias operaciones de listas que no las cambian, sino que muestran una nueva. Dos de ellas son reversed() y sorted(). La función reversed() muestra una lista nueva en la que los elementos se encuentran en orden inverso, y sorted() muestra otra en la que los elementos están ordenados en forma ascendente.

  1. Agrega el código para revertir la lista colors. Imprime el resultado. Esta es una lista nueva que contiene los elementos de colors en orden inverso.
  2. Agrega una segunda línea de código a fin de imprimir la list original de modo que puedas ver que la lista original no cambió.
println("Reversed list: ${colors.reversed()}")
println("List: $colors")
  1. Este es el resultado de las dos listas impresas.
Reversed list: [blue, orange, green]
List: [green, orange, blue]
  1. Agrega el código para mostrar una versión ordenada de una List con la función sorted().
println("Sorted list: ${colors.sorted()}")

El resultado es una lista nueva de colores mostrados en orden alfabético. Genial.

Sorted list: [blue, green, orange]
  1. También puedes probar la función sorted() en una lista de números sin ordenar.
val oddNumbers = listOf(5, 3, 7, 1)
println("List: $oddNumbers")
println("Sorted list: ${oddNumbers.sorted()}")
List: [5, 3, 7, 1]
Sorted list: [1, 3, 5, 7]

A esta altura, ya puedes ver la utilidad de crear listas. Sin embargo, sería bueno poder modificarlas después de su creación, así que veamos las listas mutables.

3. Introducción a las listas mutables

Las listas mutables son listas que pueden modificarse luego de su creación. Puedes agregar, quitar o cambiar elementos. También podrás hacer todo aquello que puedes hacer con las listas de solo lectura. Las listas mutables son de tipo MutableList, y puedes crearlas llamando a mutableListOf().

Crea una MutableList

  1. Borra el código existente en main().
  2. Dentro de la función main(), crea una lista mutable vacía y asígnala a una variable val llamada entrees.
val entrees = mutableListOf()

Si intentas ejecutar tu código, se mostrará el siguiente error:

Not enough information to infer type variable T

Como se mencionó anteriormente, cuando creas una MutableList o una List, Kotlin intenta inferir qué tipo de elementos contiene la lista a partir de los argumentos pasados. Por ejemplo, si escribes listOf("noodles"), Kotlin infiere que quieres crear una lista de String. Cuando inicializas una lista vacía sin elementos, Kotlin no puede inferir el tipo de elementos, por lo que deberás indicar ese tipo de forma explícita. Para ello, agrega el tipo entre corchetes angulares justo después de mutableListOf o listOf. En la documentación, puedes ver esto como <T>, donde T representa el parámetro del tipo.

  1. Corrige la declaración variable a fin de especificar que quieres crear una lista mutable de tipo String.
val entrees = mutableListOf<String>()

Otra forma de corregir el error es especificar el tipo de datos de la variable por adelantado.

val entrees: MutableList<String> = mutableListOf()
  1. Imprime la lista.
println("Entrees: $entrees")
  1. El resultado muestra [] para una lista vacía.
Entrees: []

Agrega elementos a una lista

Las listas mutables se vuelven interesantes cuando agregas, quitas y actualizas elementos.

  1. Agrega "noodles" a la lista con entrees.add("noodles").. La función add() muestra true si el elemento se agregó correctamente a la lista. De lo contrario, muestra false.
  2. Imprime la lista para confirmar que se agregó "noodles".
println("Add noodles: ${entrees.add("noodles")}")
println("Entrees: $entrees")

El resultado es el siguiente:

Add noodles: true
Entrees: [noodles]
  1. Agrega otro elemento "spaghetti" a la lista.
println("Add spaghetti: ${entrees.add("spaghetti")}")
println("Entrees: $entrees")

La lista entrees resultante ahora contiene dos elementos.

Add spaghetti: true
Entrees: [noodles, spaghetti]

En lugar de agregar elementos uno por uno con add(), puedes agregar varios elementos a la vez usando addAll() y pasando una lista.

  1. Crea una lista de moreItems. No tendrás que cambiarla, así que créala como un val inmutable.
val moreItems = listOf("ravioli", "lasagna", "fettuccine")
  1. Con addAll(), agrega todos los elementos de la lista nueva a entrees. Imprime la lista resultante.
println("Add list: ${entrees.addAll(moreItems)}")
println("Entrees: $entrees")

El resultado muestra que la lista se agregó de manera satisfactoria. Ahora, la lista entrees tiene un total de 5 elementos.

Add list: true
Entrees: [noodles, spaghetti, ravioli, lasagna, fettuccine]
  1. Ahora agrega un número a esta lista.
entrees.add(10)

El error que se muestra es el siguiente:

The integer literal does not conform to the expected type String

Esto se debe a que la lista entrees espera elementos del tipo String y estás intentando agregar un Int. Recuerda que, cuando agregas elementos a una lista, estos deben ser del tipo de datos correcto. De lo contrario, se mostrará un error de compilación. De esta manera, Kotlin garantiza que tu código resulte más seguro mediante la seguridad de tipo.

  1. Quita la línea de código incorrecta de modo que se compile tu código.

Quita elementos de una lista

  1. Llama a remove() para quitar el elemento "spaghetti" de la lista. Vuelve a imprimir la lista.
println("Remove spaghetti: ${entrees.remove("spaghetti")}")
println("Entrees: $entrees")
  1. Cuando quitas "spaghetti", se muestra un valor verdadero porque el elemento estaba presente en la lista y se pudo quitar correctamente. La lista ahora solo tiene 4 elementos.
Remove spaghetti: true
Entrees: [noodles, ravioli, lasagna, fettuccine]
  1. ¿Qué ocurre si intentas quitar un elemento que no existe en la lista? Quita el elemento "rice" de la lista con entrees.remove("rice").
println("Remove item that doesn't exist: ${entrees.remove("rice")}")
println("Entrees: $entrees")

El método remove() muestra false, ya que el elemento no existe y, por lo tanto, no se puede quitar. La lista permanece sin modificación con los mismos 4 elementos. Salida:

Remove item that doesn't exist: false
Entrees: [noodles, ravioli, lasagna, fettuccine]
  1. También puedes especificar el índice del elemento que deseas quitar. Usa removeAt() a fin de quitar el elemento del índice 0.
println("Remove first element: ${entrees.removeAt(0)}")
println("Entrees: $entrees")

El valor que se muestra de removeAt(0) es el primer elemento ("noodles"), el cual se quitó de la lista. La lista entrees ahora tiene 3 elementos restantes.

Remove first element: noodles
Entrees: [ravioli, lasagna, fettuccine]
  1. Si quieres borrar la lista completa, puedes llamar a clear().
entrees.clear()
println("Entrees: $entrees")

El resultado ahora muestra una lista vacía.

Entrees: []
  1. Kotlin te permite verificar si una lista está vacía mediante la función isEmpty(). Imprime entrees.isEmpty()..
println("Empty? ${entrees.isEmpty()}")

El resultado debería ser verdadero, ya que la lista está vacía (tiene 0 elementos).

Empty? true

El método isEmpty() resulta útil si deseas realizar una operación en una lista o acceder a un elemento determinado, pero primero asegúrate de que la lista no esté vacía.

Aquí se muestra todo el código que escribiste para las listas mutables. Los comentarios son opcionales.

fun main() {
    val entrees = mutableListOf<String>()
    println("Entrees: $entrees")

    // Add individual items using add()
    println("Add noodles: ${entrees.add("noodles")}")
    println("Entrees: $entrees")
    println("Add spaghetti: ${entrees.add("spaghetti")}")
    println("Entrees: $entrees")

    // Add a list of items using addAll()
    val moreItems = listOf("ravioli", "lasagna", "fettuccine")
    println("Add list: ${entrees.addAll(moreItems)}")
    println("Entrees: $entrees")

    // Remove an item using remove()
    println("Remove spaghetti: ${entrees.remove("spaghetti")}")
    println("Entrees: $entrees")
    println("Remove item that doesn't exist: ${entrees.remove("rice")}")
    println("Entrees: $entrees")

    // Remove an item using removeAt() with an index
    println("Remove first element: ${entrees.removeAt(0)}")
    println("Entrees: $entrees")

    // Clear out the list
    entrees.clear()
    println("Entrees: $entrees")

    // Check if the list is empty
    println("Empty? ${entrees.isEmpty()}")
}

4. Establece un bucle a través de una lista

Para realizar una operación en cada elemento de una lista, puedes realizar un bucle a lo largo de la lista (lo que también se conoce como iterar a través de la lista). Puedes usar los bucles con Lists y MutableLists.

Bucles while

Uno de los tipos de bucle es el bucle while. Un bucle while comienza con la palabra clave while en Kotlin. Contiene un bloque de código (dentro de llaves) que se ejecuta una y otra vez, siempre que la expresión entre paréntesis sea verdadera. A fin de evitar que el código se ejecute por siempre (lo que se denomina bucle infinito), el bloque de código debe contener una lógica que cambie el valor de la expresión para que finalmente la expresión resulte falsa y se deje de ejecutar el bucle. En ese punto, saldrás del bucle while y se seguirá ejecutando el código que viene después de este.

while (expression) {
    // While the expression is true, execute this code block
}

Usa un bucle while para iterar a través de una lista. Crea una variable a fin de hacer un seguimiento del index que estás viendo en la lista. Esta variable index se incrementará en 1 cada vez hasta que llegues al último índice de la lista, después de lo cual saldrás del bucle.

  1. Borra el código existente en el Playground de Kotlin y deja una función main() vacía.
  2. Supongamos que estás organizando una fiesta. Crea una lista que represente la cantidad de invitados de cada familia que confirmaron asistencia. La primera familia dijo que asistirán 2 personas de su familia. La segunda familia indicó que asistirán 4 personas, y así sucesivamente.
val guestsPerFamily = listOf(2, 4, 1, 3)
  1. Calcula cuántos invitados habrá. Escribe un bucle a efectos de encontrar la respuesta. Crea una var para la cantidad total de invitados y luego inicialízala en 0.
var totalGuests = 0
  1. Inicializa una var para la variable index, como se describió más arriba.
var index = 0
  1. Escribe un bucle while a fin de iterar a través de la lista. La condición es seguir ejecutando el bloque de código siempre que el valor de index sea menor que el tamaño de la lista.
while (index < guestsPerFamily.size) {

}
  1. Dentro del bucle, obtén el elemento de la lista del index actual y agrégalo a la variable que representa la cantidad total de invitados. Recuerda que totalGuests += guestsPerFamily[index] es lo mismo que totalGuests = totalGuests + guestsPerFamily[index]..

Observa que la última línea del bucle aumenta la variable index en 1 usando index++, de modo que la siguiente iteración del bucle observará la siguiente familia de la lista.

while (index < guestsPerFamily.size) {
    totalGuests += guestsPerFamily[index]
    index++
}
  1. Después del bucle while, puedes imprimir el resultado.
while ... {
    ...
}
println("Total Guest Count: $totalGuests")
  1. Ejecuta el programa, y el resultado será el indicado a continuación. Para verificar que esta sea la respuesta correcta, suma los números de la lista manualmente.
Total Guest Count: 10

Este es el fragmento de código completo:

val guestsPerFamily = listOf(2, 4, 1, 3)
var totalGuests = 0
var index = 0
while (index < guestsPerFamily.size) {
    totalGuests += guestsPerFamily[index]
    index++
}
println("Total Guest Count: $totalGuests")

Con un bucle while, era necesario escribir código para crear una variable que hiciera un seguimiento del índice, obtuviera el elemento en el índice de la lista y actualizara esa variable de índice. Existe una forma aún más rápida y concisa de iterar a través de una lista: usar bucles for.

Bucles for

Un bucle for es otro tipo de bucle. Este hace que la operación de bucle sea mucho más fácil. Comienza con la palabra clave for en Kotlin y el bloque de código entre llaves. La condición para ejecutar ese bloque se indica entre paréntesis.

for (number in numberList) {
   // For each element in the list, execute this code block
}

En este ejemplo, la variable number se establece igual que el primer elemento de numberList, y se ejecuta el bloque de código. Luego, la variable number se actualiza automáticamente y pasa a ser el siguiente elemento de numberList, y se vuelve a ejecutar el bloque de código. Esto se repite para cada elemento de la lista hasta que se alcanza el final de numberList.

  1. Borra el código existente en el Playground de Kotlin y reemplázalo por el siguiente:
fun main() {
    val names = listOf("Jessica", "Henry", "Alicia", "Jose")
}
  1. Agrega un bucle for a fin de imprimir todos los elementos de la lista names.
for (name in names) {
    println(name)
}

Esto es mucho más fácil que si tuvieras que escribir esto como un bucle while.

  1. El resultado es el siguiente:
Jessica
Henry
Alicia
Jose

Una operación común en las listas es hacer algo con cada elemento de ellas.

  1. Modifica el bucle para imprimir también la cantidad de caracteres del nombre de esa persona. Sugerencia: Puedes usar la propiedad length de una String a efectos de encontrar la cantidad de caracteres de esa String.
val names = listOf("Jessica", "Henry", "Alicia", "Jose")
for (name in names) {
    println("$name - Number of characters: ${name.length}")
}

Salida:

Jessica - Number of characters: 7
Henry - Number of characters: 5
Alicia - Number of characters: 6
Jose - Number of characters: 4

El código del bucle no cambió la List original. Solo afectó lo que se imprimió.

En esencia, puedes escribir las instrucciones sobre lo que debe suceder para 1 elemento de la lista, y el código se ejecutará para todos los elementos de ella. Usar un bucle puede evitar que escribas mucho código repetitivo.

Ahora que experimentaste con la creación y el uso tanto de listas comunes como de listas mutables, y aprendiste acerca de los bucles, es momento de aplicar este conocimiento en un caso de uso de muestra.

5. Combina toda la información

Cuando se trata de pedir comida en un restaurante local, suele haber varios elementos en un único pedido de un cliente. El uso de listas es ideal para almacenar información sobre un pedido. También aprovecharás tus conocimientos sobre clases y herencia a fin de crear un programa de Kotlin más sólido y escalable en lugar de colocar todo el código en la función main().

En la siguiente serie de tareas, crea un programa de Kotlin que permita pedir diferentes combinaciones de alimentos.

Primero, observa este resultado de ejemplo del código final. ¿Puedes intercambiar ideas acerca de los tipos de clases que necesitarías para organizar todos estos datos?

Order #1
Noodles: $10
Total: $10

Order #2
Noodles: $10
Vegetables Chef's Choice: $5
Total: $15

En el resultado, notarás lo siguiente:

  • Hay una lista de pedidos.
  • Cada pedido tiene un número.
  • Cada pedido puede contener una lista de elementos, como fideos y vegetales.
  • Cada elemento tiene un precio.
  • Cada pedido tiene un precio total, que es la suma de los precios de los elementos individuales.

Puedes crear una clase que represente un Order y una clase que represente cada alimento, como Noodles o Vegetables. Además, puedes observar que Noodles y Vegetables tienen algunas similitudes porque son alimentos y cada uno tiene un precio. Puedes considerar crear una clase Item con propiedades compartidas de las que puedan heredar las clases Noodle y Vegetable. De esa manera, no necesitarás duplicar la lógica en las clases Noodle y Vegetable.

  1. Se te proporcionará el siguiente código de partida. Los desarrolladores profesionales suelen leer el código de otras personas, por ejemplo, si se van a unir a un proyecto nuevo o van a complementar una función que creó otra persona. Es importante poder leer y comprender código.

Dedica un tiempo a analizar este código y comprender lo que sucede. Copia y pega este código en el Playground de Kotlin y ejecútalo. Asegúrate de borrar todo el código existente en el Playground de Kotlin antes de pegar este código nuevo. Revisa el resultado y observa si eso te ayuda a comprender mejor el código.

open class Item(val name: String, val price: Int)

class Noodles : Item("Noodles", 10)

class Vegetables : Item("Vegetables", 5)

fun main() {
    val noodles = Noodles()
    val vegetables = Vegetables()
    println(noodles)
    println(vegetables)
}
  1. Deberías ver un resultado similar a esto:
Noodles@5451c3a8
Vegetables@76ed5528

Aquí te ofrecemos una explicación más detallada del código. Primero, hay una clase llamada Item, en la que el constructor toma 2 parámetros: un name para el elemento (como una string) y un price (como un número entero). Ninguna de las propiedades cambia después de que se pasan, por lo que se marcan como val. Dado que Item es una clase superior y las subclases se extienden desde esa clase, la clase se marca con la palabra clave open.

El constructor de clase Noodles no tiene parámetros, pero se extiende desde Item y llama al constructor de la superclase pasando "Noodles" como el nombre y el precio de 10. La clase Vegetables es similar, pero llama al constructor de la superclase con "Vegetables" y un precio de 5.

La función main() inicializa nuevas instancias de objetos de las clases Noodles y Vegetables, y las imprime en el resultado.

Anula el método toString()

Cuando imprimes una instancia de objeto en el resultado, se llama al método toString() del objeto. En Kotlin, cada clase hereda automáticamente el método toString(). La implementación predeterminada de este método muestra el tipo de objeto con una dirección de memoria para la instancia. Debes anular toString() a fin de mostrar algo más significativo y fácil de usar que Noodles@5451c3a8 y Vegetables@76ed5528.

  1. Dentro de la clase Noodles, anula el método toString() y haz que muestre el name. Recuerda que Noodles hereda la propiedad name de su clase superior Item.
class Noodles : Item("Noodles", 10) {
   override fun toString(): String {
       return name
   }
}
  1. Repite lo mismo para la clase Vegetables.
class Vegetables() : Item("Vegetables", 5) {
   override fun toString(): String {
       return name
   }
}
  1. Ejecuta tu código. El resultado se ve mejor ahora:
Noodles
Vegetables

En el siguiente paso, cambiarás el constructor de la clase Vegetables para que tome algunos parámetros y actualizarás el método toString() de modo que refleje esa información adicional.

Personaliza los vegetales de un pedido

Para que las sopas de fideos resulten más interesantes, puedes incluir diferentes vegetales en tus pedidos.

  1. En la función main(), en lugar de inicializar una instancia de Vegetables sin argumentos de entrada, pasa tipos específicos de vegetales que el cliente quiera.
fun main() {
    ...
    val vegetables = Vegetables("Cabbage", "Sprouts", "Onion")
    ...
}

Si intentas compilar tu código ahora, verás el siguiente error:

Too many arguments for public constructor Vegetables() defined in Vegetables

Ahora estás pasando 3 argumentos de string al constructor de la clase Vegetables, por lo que deberás modificar la clase Vegetables.

  1. Actualiza el encabezado de la clase Vegetables para que tome 3 parámetros de string, como se muestra en el siguiente código.
class Vegetables(val topping1: String,
                 val topping2: String,
                 val topping3: String) : Item ("Vegetables", 5) {
  1. Ahora, tu código volverá a compilarse. Sin embargo, esta solución solo funciona si los clientes desean pedir exactamente tres vegetales. Si quisieran pedir un vegetal o cinco, no tendrían suerte.
  2. En lugar de usar una propiedad para cada vegetal, puedes corregir el problema aceptando una lista de vegetales (que puede tener cualquier longitud) en el constructor para la clase Vegetables. La List debe contener solo Strings, por lo que el tipo del parámetro de entrada es List<String>.
class Vegetables(val toppings: List<String>) : Item("Vegetables", 5) {

Esta no es la solución más elegante porque, en main(), tendrías que cambiar tu código para crear una lista de los ingredientes antes de pasarla al constructor Vegetables.

Vegetables(listOf("Cabbage", "Sprouts", "Onion"))

Hay una forma aún mejor de resolver esto.

  1. En Kotlin, el modificador vararg te permite pasar una cantidad variable de argumentos del mismo tipo a una función o constructor. De esta manera, puedes proporcionar los diferentes vegetales como strings individuales en lugar de hacerlo como una lista.

Cambia la definición de clase de Vegetables de modo que tome un vararg toppings de tipo String.

class Vegetables(vararg val toppings: String) : Item("Vegetables", 5) {
  1. Este código en la función main() ahora se ejecutará correctamente. Puedes crear una instancia de Vegetables si pasas cualquier cantidad de strings de ingredientes.
fun main() {
    ...
    val vegetables = Vegetables("Cabbage", "Sprouts", "Onion")
    ...
}
  1. Ahora, modifica el método toString() de la clase Vegetables a fin de que muestre una String que también mencione los ingredientes en este formato: Vegetables Cabbage, Sprouts, Onion.

Comienza con el nombre del elemento (Vegetables). Luego, usa el método joinToString() para unir todos los ingredientes en una sola string. Agrega estas dos partes mediante el operador + con un espacio intermedio.

class Vegetables(vararg val toppings: String) : Item("Vegetables", 5) {
    override fun toString(): String {
        return name + " " + toppings.joinToString()
    }
}
  1. Ejecuta el programa. Debería mostrar el siguiente resultado:
Noodles
Vegetables Cabbage, Sprouts, Onion
  1. Cuando escribas programas, deberás considerar todas las entradas posibles. Cuando no hay argumentos de entrada para el constructor Vegetables, controla el método toString() de una manera más fácil de usar.

Como el cliente quiere vegetales, pero no dijo cuáles, una solución es darle la opción predeterminada de la selección del chef.

Actualiza el método toString() para mostrar Vegetables Chef's Choice si no se pasó ningún ingrediente. Usa el método isEmpty() que aprendiste antes.

override fun toString(): String {
    if (toppings.isEmpty()) {
        return "$name Chef's Choice"
    } else {
        return name + " " + toppings.joinToString()
    }
}
  1. Actualiza la función main() a fin de probar ambas posibilidades para crear una instancia de Vegetables sin argumentos de constructor y con varios argumentos.
fun main() {
    val noodles = Noodles()
    val vegetables = Vegetables("Cabbage", "Sprouts", "Onion")
    val vegetables2 = Vegetables()
    println(noodles)
    println(vegetables)
    println(vegetables2)
}
  1. Verifica que el resultado sea el esperado.
Noodles
Vegetables Cabbage, Sprouts, Onion
Vegetables Chef's Choice

Crea un pedido

Ahora que tienes algunos alimentos, puedes crear un pedido. Encapsula la lógica para un pedido dentro de una clase Order en tu programa.

  1. Piensa en las propiedades y los métodos que serían razonables para la clase Order. Por si te resulta útil, a continuación, te volvemos a mostrar un resultado de ejemplo del código final.
Order #1
Noodles: $10
Total: $10

Order #2
Noodles: $10
Vegetables Chef's Choice: $5
Total: $15

Order #3
Noodles: $10
Vegetables Carrots, Beans, Celery: $5
Total: $15

Order #4
Noodles: $10
Vegetables Cabbage, Onion: $5
Total: $15

Order #5
Noodles: $10
Noodles: $10
Vegetables Spinach: $5
Total: $25
  1. Es posible que llegues a lo siguiente:

Clase de pedido

Propiedades: número de pedido, lista de elementos

Métodos: agregar artículo, agregar varios artículos, imprimir el resumen del pedido (incluido el precio)

  1. Concentrémonos primero en las propiedades: ¿cuál debería ser el tipo de datos de cada propiedad? ¿Deberían ser públicas o privadas para la clase? ¿Deberían pasarse como argumentos o definirse dentro de la clase?
  2. Hay varias formas de implementar esto, pero a continuación te mostramos una solución. Crea un Order de class que tenga un parámetro de constructor de número entero orderNumber.
class Order(val orderNumber: Int)
  1. Ya que es posible que no conozcas todos los elementos del pedido por adelantado, no pidas que se pase la lista de elementos como un argumento. En su lugar, se la puede declarar como una variable de clase de nivel superior e inicializarla como un objeto MutableList vacío que puede contener elementos del tipo Item. Marca la variable private para que solo esta clase pueda modificar esa lista de elementos directamente. Esto evitará que el código fuera de esta clase modifique la lista de forma inesperada.
class Order(val orderNumber: Int) {
    private val itemList = mutableListOf<Item>()
}
  1. Agrega los métodos a la definición de la clase. Puedes elegir nombres razonables para cada método y, por el momento, dejar en blanco la lógica de implementación dentro de cada uno. También debes decidir qué argumentos de funciones y valores mostrados deberán solicitarse.
class Order(val orderNumber: Int) {
   private val itemList = mutableListOf<Item>()

   fun addItem(newItem: Item) {
   }

   fun addAll(newItems: List<Item>) {
   }

   fun print() {
   }
}
  1. El método addItem() parece más sencillo, así que implementa esa función primero. Este toma un Item nuevo, y el método debe agregarlo al itemList.
fun addItem(newItem: Item) {
    itemList.add(newItem)
}
  1. A continuación, implementa el método addAll(). Este toma una lista de solo lectura de los elementos. Agrega todos esos elementos a la lista interna de elementos.
fun addAll(newItems: List<Item>) {
    itemList.addAll(newItems)
}
  1. Luego, implementa el método print(), que imprime un resumen de todos los elementos y sus precios en el resultado, así como el precio total del pedido.

Primero imprime el número de pedido. Luego, usa un bucle para iterar a través de todos los elementos de la lista de pedidos. Imprime cada elemento y su precio correspondiente. Además, conserva un subtotal del precio para seguir sumando valores a medida que avancen las iteraciones. Imprime el precio total al final. Implementa esta lógica tú mismo. Si necesitas ayuda, consulta la solución que aparece más abajo.

Es posible que quieras incluir el símbolo de moneda para que el resultado sea más fácil de leer. A continuación, te mostramos una manera de implementar la solución. Este código utiliza el símbolo de moneda $, pero puedes cambiarlo por el de tu moneda local.

fun print() {
    println("Order #${orderNumber}")
    var total = 0
    for (item in itemList) {
        println("${item}: $${item.price}")
        total += item.price
    }
    println("Total: $${total}")
}

Para cada item en itemList, muestra el item (que activa la llamada a toString() en el item) seguido del price del elemento. También antes del bucle, inicializa una variable de tipo entero total en 0. Luego, continúa haciendo la suma total agregando el precio del elemento actual al total.

Crea instancias de Order

  1. Prueba tu código creando instancias de Order dentro de la función main(). Primero, borra lo que tienes en la función main().
  2. Puedes usar estos pedidos de muestra o crear otros propios. Experimenta con diferentes combinaciones de elementos dentro de los pedidos y asegúrate de probar todas las rutas de código en tu proyecto. Por ejemplo, prueba los métodos addItem() y addAll() dentro de la clase Order, crea instancias de Vegetables con argumentos y sin argumentos, etcétera.
fun main() {
    val order1 = Order(1)
    order1.addItem(Noodles())
    order1.print()

    println()

    val order2 = Order(2)
    order2.addItem(Noodles())
    order2.addItem(Vegetables())
    order2.print()

    println()

    val order3 = Order(3)
    val items = listOf(Noodles(), Vegetables("Carrots", "Beans", "Celery"))
    order3.addAll(items)
    order3.print()
}
  1. El resultado del código anterior debería ser el siguiente. Verifica que se haya calculado correctamente el precio total.
Order #1
Noodles: $10
Total: $10

Order #2
Noodles: $10
Vegetables Chef's Choice: $5
Total: $15

Order #3
Noodles: $10
Vegetables Carrots, Beans, Celery: $5
Total: $15

Bien hecho. ¡Ahora sí parecen pedidos de comidas!

6. Mejora tu código

Mantén una lista de pedidos

Si estuvieras compilando un programa que de verdad se fuera a usar en una tienda de fideos, sería razonable llevar un registro de la lista de todos los pedidos de los clientes.

  1. Crea una lista para almacenar todos los pedidos. ¿Es una lista de solo lectura o una mutable?
  2. Agrega este código a la función main(). Inicializa la lista para que esté vacía al principio. Luego, a medida que se cree cada pedido, agrégalo a la lista.
fun main() {
    val ordersList = mutableListOf<Order>()

    val order1 = Order(1)
    order1.addItem(Noodles())
    ordersList.add(order1)

    val order2 = Order(2)
    order2.addItem(Noodles())
    order2.addItem(Vegetables())
    ordersList.add(order2)

    val order3 = Order(3)
    val items = listOf(Noodles(), Vegetables("Carrots", "Beans", "Celery"))
    order3.addAll(items)
    ordersList.add(order3)
}

Como los pedidos se agregan con el tiempo, la lista debe ser una MutableList de tipo Order. Luego, usa el método add() en MutableList para agregar cada pedido.

  1. Una vez que tengas una lista de pedidos, podrás usar un bucle a fin de imprimir cada uno. Imprime una línea en blanco entre pedidos para que el resultado resulte más fácil de leer.
fun main() {
    val ordersList = mutableListOf<Order>()

    ...

    for (order in ordersList) {
        order.print()
        println()
    }
}

Esto quita el código duplicado de nuestra función main() y facilita la lectura del código. El resultado debería ser el mismo que antes.

Implementa un patrón Builder para instancias de Order

Si quieres hacer que tu código Kotlin sea más conciso, puedes usar el patrón Builder para crear pedidos. El patrón Builder es un patrón de diseño de programación que te permite compilar un objeto complejo en un enfoque paso a paso.

  1. En lugar de mostrar Unit (o no mostrar nada) de los métodos addItem() y addAll() en la clase Order, muestra el Order modificado. Kotlin proporciona la palabra clave this para hacer referencia a la instancia del objeto actual. Dentro de los métodos addItem() y addAll(), muestra el Order actual cuando muestres this.
fun addItem(newItem: Item): Order {
    itemList.add(newItem)
    return this
}

fun addAll(newItems: List<Item>): Order {
    itemList.addAll(newItems)
    return this
}
  1. En la función main(), ahora puedes encadenar las llamadas, como se muestra en el siguiente código. Este código crea un Order nuevo y aprovecha el patrón Builder.
val order4 = Order(4).addItem(Noodles()).addItem(Vegetables("Cabbage", "Onion"))
ordersList.add(order4)

Order(4) muestra una instancia de Order, en la que puedes llamar a addItem(Noodles()). El método addItem() muestra la misma instancia de Order (con el estado nuevo), y puedes volver a llamar a addItem() en ella con vegetales. El resultado de Order que se muestra se puede almacenar en la variable order4.

El código existente para crear Orders aún funciona, así que puedes dejarlo tal como está. Si bien no es obligatorio encadenar estas llamadas, es una práctica común y recomendada que aprovecha el valor que se muestra de la función.

  1. En este punto, no es necesario que almacenes el pedido en una variable. En la función main() (antes del bucle final para imprimir los pedidos), crea un Order directamente y agrégalo a la orderList. El código también resultará más fácil de leer si cada llamada de método se coloca en su propia línea.
ordersList.add(
    Order(5)
        .addItem(Noodles())
        .addItem(Noodles())
        .addItem(Vegetables("Spinach")))
  1. Ejecuta tu código. Este es el resultado esperado:
Order #1
Noodles: $10
Total: $10

Order #2
Noodles: $10
Vegetables Chef's Choice: $5
Total: $15

Order #3
Noodles: $10
Vegetables Carrots, Beans, Celery: $5
Total: $15

Order #4
Noodles: $10
Vegetables Cabbage, Onion: $5
Total: $15

Order #5
Noodles: $10
Noodles: $10
Vegetables Spinach: $5
Total: $25

¡Felicitaciones por terminar este codelab!

Ya sabes lo útil que puede ser almacenar datos en listas, cambiarlas e iterar a través de ellas. Usa este conocimiento en el contexto de una app para Android a fin de mostrar una lista de datos en pantalla en el siguiente codelab.

7. Código de solución

A continuación, te mostramos el código de la solución de las clases Item, Noodles, Vegetables y Order. La función main() también muestra cómo usar esas clases. Existen varios enfoques para implementar este programa, por lo que tu código podría ser un poco diferente.

open class Item(val name: String, val price: Int)

class Noodles : Item("Noodles", 10) {
    override fun toString(): String {
        return name
    }
}

class Vegetables(vararg val toppings: String) : Item("Vegetables", 5) {
    override fun toString(): String {
        if (toppings.isEmpty()) {
            return "$name Chef's Choice"
        } else {
            return name + " " + toppings.joinToString()
        }
    }
}

class Order(val orderNumber: Int) {
    private val itemList = mutableListOf<Item>()

    fun addItem(newItem: Item): Order {
        itemList.add(newItem)
        return this
    }

    fun addAll(newItems: List<Item>): Order {
        itemList.addAll(newItems)
        return this
    }

    fun print() {
        println("Order #${orderNumber}")
        var total = 0
        for (item in itemList) {
            println("${item}: $${item.price}")
            total += item.price
        }
        println("Total: $${total}")
    }
}

fun main() {
    val ordersList = mutableListOf<Order>()

    // Add an item to an order
    val order1 = Order(1)
    order1.addItem(Noodles())
    ordersList.add(order1)

    // Add multiple items individually
    val order2 = Order(2)
    order2.addItem(Noodles())
    order2.addItem(Vegetables())
    ordersList.add(order2)

    // Add a list of items at one time
    val order3 = Order(3)
    val items = listOf(Noodles(), Vegetables("Carrots", "Beans", "Celery"))
    order3.addAll(items)
    ordersList.add(order3)

    // Use builder pattern
    val order4 = Order(4)
        .addItem(Noodles())
        .addItem(Vegetables("Cabbage", "Onion"))
    ordersList.add(order4)

    // Create and add order directly
    ordersList.add(
        Order(5)
            .addItem(Noodles())
            .addItem(Noodles())
            .addItem(Vegetables("Spinach"))
    )

    // Print out each order
    for (order in ordersList) {
        order.print()
        println()
    }
}

8. Resumen

Kotlin brinda funcionalidades que te ayudan a administrar y manipular colecciones de datos con mayor facilidad mediante la biblioteca estándar de Kotlin. Se puede definir una colección como una cantidad de objetos del mismo tipo de datos. Existen diferentes tipos de colecciones básicas en Kotlin: listas, conjuntos y mapas. En particular, este codelab se enfocó en listas. Obtendrás más información sobre conjuntos y mapas en codelabs futuros.

  • Una lista es una colección ordenada de elementos de un tipo específico, como una lista de Strings.
  • El índice es la posición de número entero que refleja la posición del elemento (p. ej., myList[2]).
  • En una lista, el primer elemento está en el índice 0 (p. ej., myList[0]) y el último elemento es myList.size-1 (p. ej., myList[myList.size-1] o myList.last()).
  • Existen dos tipos de listas: List y MutableList.
  • Una List es de solo lectura y no se puede modificar una vez inicializada. Sin embargo, puedes aplicar operaciones como sorted() y reversed(), que muestran una lista nueva sin cambiar la original.
  • Una MutableList se puede modificar después de su creación, por ejemplo, agregando, quitando o modificando elementos.
  • Puedes agregar una lista de elementos a una lista mutable con addAll().
  • Usa un bucle while para ejecutar un bloque de código hasta que la expresión resulte falsa y salgas del bucle.

while (expression) {

// While the expression is true, execute this code block

}

  • Usa un bucle for para iterar a través de todos los elementos de una lista:

for (item in myList) {

// Execute this code block for each element of the list

}

  • El modificador vararg te permite pasar una cantidad variable de argumentos a una función o constructor.

9. Más información