# Collections en Go: Arrays, Slices y Maps Explicados

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.

```go
// 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**

| **Aspecto** | **Java** | **Go** |
| --- | --- | --- |
| Tipo | Objeto (referencia) | Valor primitivo |
| Pasar a función | Se pasa referencia | Se copia completamente |
| Tamaño | Puede cambiar | Fijo en tiempo de compilación |

```go
// 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**

```go
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)
    

```go
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**

```go
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:

```go
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**:

```go
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()`:

```go
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**

```go
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**

```go
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**

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

### **Verificar si una Clave Existe**

Esta es una característica única de Go:

```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**

```go
delete(m3, "banana")
```

### **Iterar sobre Maps**

```go
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**

```go
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:

```go
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ística** | **Java** | **Go** |
| --- | --- | --- |
| Lista dinámica | `ArrayList<T>` | `[]T` (slice) |
| Map clave-valor | `HashMap<K, V>` | `map[K]V` |
| Array fijo | `T[]` (objeto) | `[N]T` (valor) |
| Verificar existencia | `containsKey()` | `value, exists := m[key]` |
| Orden garantizado | `LinkedHashMap` | No (usa slice + map) |
| Nil/Null | `null` | `nil` |

## **Mejores Prácticas**

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

```go
// ✅ 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

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

### **3\. Siempre Verifica Existencia en Maps**

```go
// ✅ 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**

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

### **5\. Usa Slices en Lugar de Arrays**

```go
// ✅ 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**

```go
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**

```go
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`

```go
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](https://github.com/tu-usuario/go-mastery-lab).

Enjoy!

José Díaz

+51 939965148
