Índice de contenido
- ¿Cuando emplear listas y cuando?
- 1. Cuándo emplear Listas (List, MutableList)
- 2. Cuándo emplear Arrays (Array, IntArray, etc.)
- Los Arrays en Kotlin: Estructura de bajo nivel (La forma "Legacy")
- Variaciones en los métodos arrayOf, arrayOfNULLs y emptyArray
- emptyArray
- arrayOfNULLs
- Comparativa de tipos de Arrays
- El concepto de "Boxing" (Boxeo)
-
¿Por qué es ineficiente arrayOf
? - Las listas en Kotlin
- Listas inmutables
- Listas Mutables
- Eliminar valores de la lista mediante valor e índice.
- Listas mutables e inmutables
- Listas SetOf
- Ejemplos de operaciones en Listas
- 1. Operadores Plus (+) y Minus (-)
- 2. Data Classes en Listas
- 3. Predicados (Filter, Map, Any, Find)
- Combinándolo todo
- Funciones de Filtrado y Transformación
- Conclusión
Los arrays o arreglos y las listas son un elemento fundamental en cualquier lenguaje de programación que permiten manejar colecciones de valores o de datos de un mismo tipo ya sean entre las primitivas de Kotlin o nuestros propios objetos; en Kotlin.
Como principio fundamental en Kotlin, que es el manejo de los tipos mutables y los tipos inmutables, este principio también se extiende a los array y listas que veremos a continuación.
Anteriormente vimos los companion Objects para manejar los Static y Factories en Kotlin.
¿Cuando emplear listas y cuando?
En resumen, las listas representan la forma moderna de trabajar en Kotlin. Ambas estructuras nos permiten guardar un conjunto de elementos del mismo tipo (enteros, strings u objetos de una base de datos).
Por ejemplo, si estás desarrollando una aplicación de academia (como la que ves de fondo), tendrás una clase Libro y querrás devolver un listado para pintarlo en pantalla. Para eso utilizamos estas estructuras.
- Listas: Son la opción preferida el 95% de las veces. Permiten variar su tamaño (si son mutables), manipular datos con programación funcional (filtros, ordenaciones) y cuentan con la protección contra nulos característica de Kotlin.
- Arrays: Son una estructura de más bajo nivel. Se utilizan principalmente por interoperabilidad con Java o cuando se busca una eficiencia ligeramente superior. Tienen un tamaño fijo conocido desde su creación.
1. Cuándo emplear Listas (List, MutableList)
Es la opción preferida en el 95% de los casos en Kotlin.
- Tamaño dinámico: Cuando no sabes cuántos elementos vas a tener (por ejemplo, datos que vienen de una base de datos o de una API).
- Manipulación de datos: Si necesitas añadir (add), eliminar (remove) o filtrar elementos fácilmente.
- Programación Funcional: Las listas tienen métodos mucho más potentes para transformar datos como map, filter, reduce y groupBy.
- Seguridad de tipos: Las listas en Kotlin son "covariantes" (puedes tratar una List<String> como una List<Any>), lo que las hace más flexibles en el diseño de software.
2. Cuándo emplear Arrays (Array, IntArray, etc.)
Los arrays se consideran una estructura de "bajo nivel". Debes usarlos en situaciones específicas:
- Rendimiento Crítico (Performance): Los arrays son ligeramente más rápidos y consumen menos memoria porque se mapean directamente a estructuras nativas de la JVM. Se usan en videojuegos o procesamiento masivo de datos.
- Tipos Primitivos: Si necesitas optimizar al máximo, los arrays especializados como IntArray o DoubleArray evitan el "boxing" (convertir números en objetos), ahorrando mucha memoria.
- Interoperabilidad con Java: Muchas librerías antiguas de Java piden String[] o int[]. En Kotlin, para pasar esos datos, debes usar Array.
- Tamaño fijo conocido: Cuando la colección jamás cambiará de tamaño (por ejemplo, los meses del año o las coordenadas de un punto 3D).
Resumen:
| Método | Tamaño | Contenido Inicial | Uso Principal |
|---|---|---|---|
| emptyArray() | Siempre 0 | Nada | Evitar valores null en funciones. |
| arrayOfNulls(size) | Definido por ti | null | Reservar espacio fijo antes de tener los datos. |
Los Arrays en Kotlin: Estructura de bajo nivel (La forma "Legacy")
Si queremos definir un array en Kotlin podemos hacerlo mediante los métodos arrayOf, arrayOfNULLs y emptyArray; cada una de estas formas comparten propiedades y funciones comunes como veremos un poco más adelante; finalmente, para crear un array con el método arrayOf tenemos:
var array = arrayOf(1,2,3,4,5)Y con esto creamos un array de enteros; si queremos imprimir el array como un String o cadena de texto, en donde separaremos cada uno de los elementos que componen el array por comas podemos emplear el siguiente método:
array.joinToString()Y obtenemos
1, 2, 3, 4, 5Variaciones en los métodos arrayOf, arrayOfNULLs y emptyArray
Si queremos declarar el array anterior con enteros empleamos el método intArrayOf:
var array = intArrayOf(1,2,3,4,5)Si por el contrario queremos que sean de double:
var array = doubleArrayOf(1.1,2.2,3.3,4.4,5.5)Por supuesto, en todos los casos podemos emplear las mismas primitivas y métodos como el que mencionamos anteriormente que es el método joinToString ():
array.joinToString() //imprime 1.1, 2.2, 3.3, 4.4, 5.5Para obtener el tamaño del array empleamos la propiedad size:
array.size // imprime 5 en cualquiera de los casos anterioresTambién podemos emplear algunas funciones para obtener o establecer valores:
var array = doubleArrayOf(1.1,2.2,3.3,4.4,5.5)
println(array.get(2)) // imprime 3.3
array.set(2, 2.5) // establecemos el valor 2.5 en el índice 2
println(array.get(2)) // imprime 2.5 También podemos obtener valores como si un array se tratase:
println(array[2]) // imprime 2.5emptyArray
Este método crea un array con un tamaño de 0.
¿Para qué se usa?
- Inicialización por defecto: Cuando necesitas inicializar una variable que debe contener un array, pero aún no tienes elementos.
- Retorno de funciones: Si una función debe devolver un array y no encuentra resultados, es mejor devolver un emptyArray que un valor null. Así evitas que la app se cierre por un error de "puntero nulo".
// Creamos un array vacío de Strings
var nombres = emptyArray<String>()
println(nombres.size) // Imprime 0arrayOfNULLs
Este método crea un array de un tamaño específico, pero llena todas sus posiciones con el valor null.
¿Para qué se usa?
Reserva de memoria: Si sabes exactamente cuántos elementos tendrá tu colección (por ejemplo, 100), pero los vas a calcular o recibir uno por uno más tarde. Es más eficiente que crear una lista y hacerla crecer.
Estructuras de datos: Se usa mucho al implementar algoritmos donde necesitas "huecos" vacíos que se llenarán según cierta lógica.
// Reservamos espacio para 3 enteros, inicialmente son null
val misNotas = arrayOfNulls<Int>(3)
misNotas[0] = 10
misNotas[1] = 8
// misNotas[2] sigue siendo null
println(misNotas.joinToString()) // ImpEn Resumen:
- emptyArray<T>(): Crea un array de tamaño cero. Es útil cuando necesitas declarar la variable pero aún no tienes los elementos ni el tamaño definitivo.
- arrayOfNulls<T>(tamaño): Crea un array de un tamaño fijo lleno de valores nulos. Sirve para reservar espacio en memoria que llenarás más adelante.
- Arrays Primitivos: Para mayor eficiencia, Kotlin ofrece versiones como intArrayOf() o doubleArrayOf(). Estos evitan el "boxeo" de objetos, trabajando directamente con tipos primitivos.
Comparativa de tipos de Arrays
Si usas arrayOf<Int>(1, 2), Kotlin crea un objeto para cada número (Integer "boxeado"), lo cual es ineficiente. Al usar intArrayOf(), Kotlin usa tipos primitivos directos del procesador, lo cual es mucho más rápido y ahorra memoria.
El concepto de "Boxing" (Boxeo)
Imagina que un número es una pequeña pieza de metal.
- Tipo Primitivo (int): Es la pieza de metal suelta. Es ligera, pequeña y el procesador la puede manejar directamente de forma súper rápida.
- Objeto (Integer): Es esa misma pieza de metal, pero metida dentro de una caja de madera forrada de terciopelo. La caja tiene etiquetas, métodos y ocupa mucho más espacio en el almacén (la memoria).
¿Por qué es ineficiente arrayOf<Int>?
Cuando creas un Array<Int>, lo que tienes en memoria es:
Una estructura para el Array.
- Referencias (flechas) que apuntan a diferentes objetos "Integer" esparcidos por la memoria.
- Cada objeto "Integer" gasta memoria extra (metadatos del objeto) además del valor numérico.
Las listas en Kotlin
Como mencionamos en un inicio y como principio fundamental en Kotlin, tenemos el manejo de los tipos mutables y los tipos inmutables lo cual llevan también al uso de las listas, así que por defecto, las listas en Kotlin son inmutables, lo que en otras palabras significa, que no pueden mutar o lo que es lo mismo que no pueden cambiar.
Por lo tanto Kotlin provee métodos para consultas, por otra parte, para las listas inmutables Kotlin provee métodos para mutar las listas como queramos, es decir método como editar o cambiar valores o remover los mismos; primero veremos las listas por defecto o las de tipo inmutables.
En resumen, en Kotlin, por defecto, las listas son inmutables (no se pueden modificar una vez creadas).
- listOf(): Crea una lista de solo lectura. No tiene métodos para añadir (add) o cambiar elementos. Esto protege la integridad de los datos.
- mutableListOf(): Si necesitas agregar, eliminar o modificar elementos, debes usar esta variante.
Listas inmutables
Para crear una lista tenemos la clase List predefinida en Kotlin que permite crear listas de objetos genéricos; es decir, de cualquier tipo; para crear una lista de elementos inmutables tenemos:
val list = listOf(1,2,3,4,5)Aquí NO se extiende una misma organización que se presenta con los Array que vimos anteriormente, y es que no podemos especificar el tipo de dato de la lista indicando el tipo de dato delante del nombre de la lista como hicimos con las Array; es decir, lo siguiente sería un código inválido:
val list = intListOf(1,2,3,4,5) // error: unresolved reference: intListOfListas Mutables
Si por el contrario necesitamos personalizar los valores de la lista, es decir emplear métodos para agregar, remover o cambiar valores, podemos emplear las listas mutables y de esta forma tenemos un abanico extra de opciones para poder personalizar o cambiar la lista cuando queramos; para ello debemos agregar "mutable" delante del "ListOf" por ejemplo, para crear una lista mutable tenemos:
val mutableList = mutableListOf(1,2,3,4,5)Para cambiar un valor, podemos hacerlo de dos formas, al igual que ocurre con los Array:
mutableList[1] = 85
println(mutableList[1]) // imprime 85O mediante:
mutableList.set(1,90)
println(mutableList.get(1)) // imprime 90En los dos bloques de código anterior además de cambiar o establecer otro valor a un índice de la lista, vimos como obtener el valor de la misma, para ello usamos el método convencional empleando los corchetes [] y entre ellos el índice o index de la posición que queremos consultar, o el método get que recibe como parámetro dicho índice, podemos emplear tanto para establecer cómo obtener valores el método que prefiramos.
Eliminar valores de la lista mediante valor e índice.
También tenemos métodos para eliminar los valores, para ello está el método removeAt que permite eliminar un valor de la lista indicando el índice de la posición que queremos eliminar y remove que permite eliminar un valor mediante el valor de la posición:
val mutableList = mutableListOf(10,20,30,400,50)
mutableList.removeAt(1)
// la lista queda mutableList [10, 30, 400, 50]
mutableList.remove(400)
// la lista queda mutableList [10, 30, 50]Listas mutables e inmutables
Como podemos apreciar, de manera predefinida Kotlin ofrece listOf y con agregar la palabra "mutable" ya podemos manejar tipos mutables, es una especie de "regla" o tip que podemos tener en cuenta cuando trabajamos con listas; con los tipos mutables, que si lo llevamos a su significado base viene siendo aquellos que pueden mutar o cambiar podemos hacer métodos para obtener valores mediante get, eliminar mediante y establecer valores mediante set cosa que no podemos hacer con las listas mutables
También las listas cuentan con métodos y propiedades en común como la propiedad size:
val mutableList = mutableListOf(1,2,3,4,5 )
mutableList.size // imprime 5 Listas SetOf
En adicional a las listas que vimos anteriormente, Kotlin también cuenta con otra estructura similar a de las listas que son las setOf que son colecciones de datos que no permiten tener valores repetidos, es decir solo permiten valores únicos es decir, que no se repitan:
val set = setOf(1,1,2,3,4,5,5,1)Imprimirá:
[1, 2, 3, 4, 5]Este tipo de colección de datos no permite modificar, eliminar o agregar nuevos valores, es decir, son de tipo inmutables; para modificarlo tenemos que emplear los tipos mutables.
val setMutable = mutableSetOf(1,2,3,4,5)Ejemplos de operaciones en Listas
1. Operadores Plus (+) y Minus (-)
A diferencia de .add() o .remove(), estos operadores no modifican la lista original, sino que devuelven una nueva lista con el cambio aplicado. Esto es ideal para mantener la inmutabilidad:
val numeros = listOf(10, 20, 30, 40)
// Plus: Crea una lista con un elemento extra
val listaMasUno = numeros + 50
// Resultado: [10, 20, 30, 40, 50]
// Minus: Crea una lista eliminando la primera aparición del elemento
val listaMenosUno = numeros - 30
// Resultado: [10, 20, 40]
println("Original: $numeros") // La original sigue intacta2. Data Classes en Listas
En el mundo real, rara vez manejas listas de simples números. Manejas objetos. Las data class son perfectas porque Kotlin ya sabe cómo comparar sus valores automáticamente.
data class Producto(val nombre: String, val precio: Double)
val carrito = listOf(
Producto("Laptop", 1200.0),
Producto("Mouse", 25.0),
Producto("Teclado", 80.0)
)
// Ahora la lista contiene objetos complejos
println(carrito[0].nombre) // Imprime: Laptop3. Predicados (Filter, Map, Any, Find)
Los predicados son condiciones (funciones lambda) que le pasas a la lista para procesar los datos de forma elegante.
val precios = listOf(10, 50, 100, 200, 500)
// FILTER: Se queda solo con lo que cumple la condición
val caros = precios.filter { it > 150 }
// Resultado: [200, 500]
// MAP: Transforma cada elemento (ej. aplicar 10% de descuento)
val descuentos = precios.map { it * 0.9 }
// Resultado: [9.0, 45.0, 90.0, 180.0, 450.0]
// ANY: Devuelve true si AL MENOS UN elemento cumple la condición
val haySuperCaros = precios.any { it > 1000 }
// Resultado: false
// FIND: Busca el primer elemento que cumpla la condición o devuelve null
val elPrimeroGrande = precios.find { it > 150 }
// Resultado: 200Combinándolo todo
Imagina que quieres los nombres de los productos que cuestan más de 50:
val nombresProdsCaros = carrito
.filter { it.precio > 50.0 } // Filtramos
.map { it.nombre } // Extraemos solo el nombre
println(nombresProdsCaros) // [Laptop, Teclado]Funciones de Filtrado y Transformación
Estas funciones son extremadamente potentes y comunes en el desarrollo moderno:
- filter: Devuelve una lista con los elementos que cumplen una condición.
- map: Transforma cada elemento de la lista en otro tipo de dato.
- any: Devuelve true si al menos un elemento cumple la condición.
- find: Busca la primera coincidencia que cumpla la regla.
Conclusión
Como puedes ver, los listados suelen manejarse mediante Data Classes para representar entidades (productos, libros, usuarios). El manejo de listas y arrays se aprende realmente practicando, algo que haremos a fondo cuando comencemos a crear nuestra aplicación en Android.