Skip to main content

Command Palette

Search for a command to run...

Collections en Go: Arrays, Slices y Maps Explicados

Updated
8 min read

En los posts anteriores exploramos structs, interfaces y métodos en Go. Hoy vamos a profundizar en las estructuras de datos fundamentales: arrays, slices y maps. Si vienes de Java, encontrarás similitudes con ArrayList y HashMap, pero también diferencias importantes que debes entender.

Arrays: Tamaño Fijo y Valores Primitivos

En Go, los arrays son diferentes a los de Java. Son valores primitivos que se copian completamente cuando los pasas a funciones.

// Arrays tienen tamaño fijo
var arr1 [5]int                    // [0 0 0 0 0]
arr2 := [5]int{1, 2, 3, 4, 5}      // [1 2 3 4 5]
arr3 := [...]int{1, 2, 3}          // El compilador cuenta: [1 2 3]

Diferencias Clave con Java

AspectoJavaGo
TipoObjeto (referencia)Valor primitivo
Pasar a funciónSe pasa referenciaSe copia completamente
TamañoPuede cambiarFijo en tiempo de compilación
// En Go, los arrays se copian por valor
arr2 := [5]int{1, 2, 3, 4, 5}
arr4 := arr2
arr4[0] = 999
fmt.Println(arr2) // [1 2 3 4 5] - No cambió
fmt.Println(arr4) // [999 2 3 4 5] - Sí cambió

En Java, esto sería diferente porque los arrays son objetos y se pasan por referencia.

¿Cuándo Usar Arrays?

Los arrays en Go se usan raramente porque:

  • Tamaño fijo es limitante

  • Las copias completas pueden ser costosas

  • Los slices son más flexibles

Usa arrays cuando:

  • Necesitas tamaño fijo garantizado en tiempo de compilación

  • Trabajas con estructuras de datos de tamaño conocido (ej: matrices 3x3)

Slices: La Estrella de Go

Los slices son lo que realmente usarás el 99% del tiempo en Go. Son similares a ArrayList en Java, pero más eficientes y con características únicas.

Crear Slices

var s1 []int                    // nil slice
s2 := []int{}                   // slice vacío (no nil)
s3 := []int{1, 2, 3, 4, 5}     // slice con valores iniciales
s4 := make([]int, 5)            // slice de longitud 5, capacidad 5
s5 := make([]int, 0, 10)        // slice de longitud 0, capacidad 10

Len vs Cap: Conceptos Fundamentales

Esta es una diferencia importante con Java:

  • len: Número de elementos actuales en el slice

  • cap: Capacidad del array subyacente (cuántos elementos puede contener sin reasignar memoria)

slice := make([]int, 3, 10) // len=3, cap=10
fmt.Printf("len: %d, cap: %d\n", len(slice), cap(slice))
// Output: len: 3, cap: 10

Piensa en len como "cuántos elementos tengo" y cap como "cuántos elementos puedo tener sin reasignar memoria".

Append: Agregar Elementos

s6 := []int{1, 2, 3}
fmt.Printf("Before: len=%d, cap=%d\n", len(s6), cap(s6))
// Before: len=3, cap=3

s6 = append(s6, 4, 5, 6)
fmt.Printf("After: len=%d, cap=%d\n", len(s6), cap(s6))
// After: len=6, cap=6 (o más, dependiendo del crecimiento)

Importante: append puede crear un nuevo array si excede la capacidad. Por eso siempre asignas el resultado: slice = append(slice, value).

Slicing: Crear Sub-slices

Los slices pueden crear "vistas" de otros slices:

original := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
slice1 := original[2:5]  // [2 3 4] - índices 2 a 4 (no incluye 5)
slice2 := original[:5]    // [0 1 2 3 4] - desde inicio hasta índice 4
slice3 := original[5:]    // [5 6 7 8 9] - desde índice 5 hasta el final

⚠️ Trampa Común: Slices Comparten Memoria

Esto es importante: Los slices creados mediante slicing comparten el array subyacente:

original := []int{0, 1, 2, 3, 4, 5}
slice := original[2:5]  // [2 3 4]

slice[0] = 999
fmt.Println(original) // [0 1 999 3 4 5] - ¡Cambió!
fmt.Println(slice)    // [999 3 4]

Solución: Si necesitas una copia independiente, usa copy():

copySlice := make([]int, len(original))
copy(copySlice, original)
copySlice[0] = 111
fmt.Println(original)  // [0 1 2 3 4 5] - No cambió
fmt.Println(copySlice) // [111 1 2 3 4 5]

Operaciones Comunes con Slices

numbers := []int{5, 2, 8, 1, 9, 3}

// Ordenar
sorted := make([]int, len(numbers))
copy(sorted, numbers)
sort.Ints(sorted)

// Filtrar (elementos mayores que 5)
filtered := make([]int, 0)
for _, n := range numbers {
    if n > 5 {
        filtered = append(filtered, n)
    }
}

// Mapear (duplicar valores)
mapped := make([]int, len(numbers))
for i, n := range numbers {
    mapped[i] = n * 2
}

// Reducir (suma)
sum := 0
for _, n := range numbers {
    sum += n
}

Maps: Estructuras Clave-Valor

Los maps en Go son similares a HashMap en Java, pero con algunas diferencias importantes.

Crear Maps

var m1 map[string]int           // nil map (no se puede usar hasta inicializar)
m2 := make(map[string]int)      // map vacío inicializado
m3 := map[string]int{           // map con valores iniciales
    "apple":  5,
    "banana": 3,
    "orange": 2,
}

Importante: Un map nil no se puede usar. Siempre inicialízalo con make() o con valores iniciales.

Acceder a Valores

value := m3["apple"]  // 5

Verificar si una Clave Existe

Esta es una característica única de Go:

value, exists := m3["grape"]
if exists {
    fmt.Printf("grape exists: %d\n", value)
} else {
    fmt.Println("grape does not exist")
}

El segundo valor (exists) te dice si la clave está presente. Si no existe, value será el valor cero del tipo (0 para int, "" para string, etc.).

Eliminar Elementos

delete(m3, "banana")

Iterar sobre Maps

for key, value := range m3 {
    fmt.Printf("%s: %d\n", key, value)
}

Nota: El orden de iteración en maps es aleatorio en Go. No confíes en un orden específico.

Obtener Claves o Valores

keys := make([]string, 0, len(m3))
values := make([]int, 0, len(m3))
for k, v := range m3 {
    keys = append(keys, k)
    values = append(values, v)
}

Restricciones de Maps

Las claves de un map deben ser comparables:

  • string, int, float64, etc.

  • slice, map, func (no son comparables)

Ejemplo Práctico: Sistema de Inventario

Vamos a construir un sistema de inventario que demuestra el uso de slices y maps:

type Product struct {
    ID    string
    Name  string
    Price float64
    Stock int
}

type Inventory struct {
    products map[string]*Product  // Map para búsqueda rápida por ID
    orders   []string              // Slice para mantener orden
}

func NewInventory() *Inventory {
    return &Inventory{
        products: make(map[string]*Product),
        orders:   make([]string, 0),
    }
}

func (inv *Inventory) AddProduct(id, name string, price float64, stock int) {
    inv.products[id] = &Product{
        ID:    id,
        Name:  name,
        Price: price,
        Stock: stock,
    }
    inv.orders = append(inv.orders, id)  // Mantener orden de inserción
}

func (inv *Inventory) GetProduct(id string) (*Product, bool) {
    product, exists := inv.products[id]
    return product, exists
}

func (inv *Inventory) ListProducts() []*Product {
    // Crear slice con capacidad pre-asignada para mejor performance
    products := make([]*Product, 0, len(inv.products))
    for _, id := range inv.orders {
        if product, exists := inv.products[id]; exists {
            products = append(products, product)
        }
    }
    return products
}

Este ejemplo muestra:

  • Map para búsqueda rápida O(1) por ID

  • Slice para mantener el orden de inserción

  • Pre-asignación de capacidad con make([]*Product, 0, len(inv.products)) para mejor performance

Comparación: Java vs Go

CaracterísticaJavaGo
Lista dinámicaArrayList<T>[]T (slice)
Map clave-valorHashMap<K, V>map[K]V
Array fijoT[] (objeto)[N]T (valor)
Verificar existenciacontainsKey()value, exists := m[key]
Orden garantizadoLinkedHashMapNo (usa slice + map)
Nil/Nullnullnil

Mejores Prácticas

1. Pre-asigna Capacidad cuando Sepas el Tamaño

// ✅ Bueno: Pre-asigna capacidad
products := make([]*Product, 0, expectedSize)

// ❌ Menos eficiente: Crece dinámicamente
products := make([]*Product, 0)

2. Usa make() con Capacidad para Maps Grandes

// Si sabes que tendrás ~100 elementos
m := make(map[string]int, 100)

3. Siempre Verifica Existencia en Maps

// ✅ Bueno
value, exists := m[key]
if exists {
    // usar value
}

// ❌ Puede ser problemático si 0 es un valor válido
value := m[key]  // ¿Es 0 porque no existe o porque el valor es 0?

4. Copia Slices cuando Necesites Independencia

// Si necesitas modificar sin afectar el original
copy := make([]int, len(original))
copy(copy, original)

5. Usa Slices en Lugar de Arrays

// ✅ Usa slices (más común)
func processItems(items []string) { ... }

// ❌ Evita arrays (menos flexible)
func processItems(items [10]string) { ... }

Errores Comunes

❌ Error 1: Usar Map Nil

var m map[string]int
m["key"] = 1  // ❌ PANIC! map is nil

// ✅ Solución
m := make(map[string]int)
m["key"] = 1

❌ Error 2: Modificar Slice Compartido Sin Quererlo

original := []int{1, 2, 3}
slice := original[1:3]
slice[0] = 999
fmt.Println(original) // [1 999 3] - ¡Cambió!

// ✅ Solución: Copia si necesitas independencia
copy := make([]int, len(original))
copy(copy, original)

❌ Error 3: Olvidar Asignar Resultado de append

slice := []int{1, 2, 3}
append(slice, 4)  // ❌ No hace nada, slice sigue siendo [1 2 3]

// ✅ Solución
slice = append(slice, 4)  // slice ahora es [1 2 3 4]

Conclusiones

Las collections en Go son poderosas pero tienen características únicas:

Arrays: Raramente usados, tamaño fijo, se copian por valor

Slices: La estructura más común, dinámicos, eficientes, comparten memoria

Maps: Similares a HashMap, pero con sintaxis más simple y verificación de existencia integrada

Conceptos clave a recordar:

  • len vs cap en slices

  • Los slices comparten el array subyacente

  • Siempre verifica existencia en maps con el segundo valor de retorno

  • Pre-asigna capacidad cuando sepas el tamaño aproximado

Una vez que entiendas estos conceptos, trabajar con collections en Go será natural y eficiente.

Próximos Pasos

En el siguiente post exploraremos el manejo de errores en Go. Aprenderemos sobre errors.Is(), errors.As(), errors.Join(), y el wrapping de errores. Este es un tema fundamental que diferencia Go de lenguajes con excepciones como Java.


¿Tienes preguntas sobre arrays, slices o maps en Go? Déjame saber en los comentarios. Y si quieres ver el código completo de estos ejemplos, puedes encontrarlo en mi repositorio go-mastery-lab.

Enjoy!

José Díaz

+51 939965148

More from this blog

JoeDayz

53 posts

Community Guy | Java Champion | AWS Architect | Software Architect