Slices and Collections
Go slices and maps vs Java List/Map — dynamic arrays and hash maps
Introduction
In this lesson, you'll learn about slices and collections in Go. Coming from Java, you already have a foundation for understanding this concept. We'll build on that knowledge while highlighting the key differences.
In Java, you're familiar with go slices and maps vs java list/map — dynamic arrays and hash maps.
Go has its own approach to go slices and maps vs java list/map — dynamic arrays and hash maps, which we'll explore step by step.
The Go Way
Let's see how Go handles this concept. Here's a typical example:
package main
import (
"fmt"
"sort"
)
func main() {
// Slice — dynamic array (like ArrayList)
nums := []int{1, 2, 3}
nums = append(nums, 4) // append returns new slice — reassign!
fmt.Println(nums[2]) // 3
fmt.Println(len(nums)) // 4
// Slice expression (like subList)
sub := nums[1:3] // [2,3] — shares memory!
_ = sub
// Sort
sort.Ints(nums) // ascending
sort.Sort(sort.Reverse(sort.IntSlice(nums))) // descending
// Map (like HashMap)
m := map[string]int{
"alice": 30,
}
m["bob"] = 25
// Safe access — two-value form
val, ok := m["alice"] // ok=false if missing
if ok { fmt.Println(val) }
// OR: default pattern
if v, ok := m["charlie"]; !ok { v = 0; _ = v }
delete(m, "alice") // remove key
// Iterate
for k, v := range m {
fmt.Printf("%s=%d
", k, v)
}
}Comparing to Java
Here's how you might have written similar code in Java:
import java.util.*;
// ArrayList<Integer> — dynamic array
List<Integer> nums = new ArrayList<>(List.of(1, 2, 3));
nums.add(4);
nums.get(2); // 3
nums.size(); // 4
nums.remove(Integer.valueOf(2)); // remove by value
nums.contains(3); // true
// Sort
Collections.sort(nums);
nums.sort(Comparator.reverseOrder());
// HashMap<K,V>
Map<String, Integer> map = new HashMap<>();
map.put("alice", 30);
map.getOrDefault("bob", 0); // safe get
map.containsKey("alice"); // true
map.remove("alice");
// Iterate
for (var entry : map.entrySet()) {
System.out.println(entry.getKey() + "=" + entry.getValue());
}
// ArrayList subList (like slice)
List<Integer> sub = nums.subList(1, 3); // [1,3) indicesYou may be used to different syntax or behavior.
append() always returns the new slice — must reassign: s = append(s, x)
You may be used to different syntax or behavior.
Slices share underlying array — modifications affect original; use copy() for independence
You may be used to different syntax or behavior.
Map access returns zero value for missing key; use two-value form (v, ok) to distinguish
You may be used to different syntax or behavior.
delete(m, key) removes; no equivalent to Java's remove(key) with return value
You may be used to different syntax or behavior.
range over map gives (key, value) pairs — iteration order is random
Step-by-Step Breakdown
1. Append vs Add
Go's append() returns a new slice (may allocate new backing array). Java's ArrayList.add() mutates in place.
list.add(42);
list.add(10);slice = append(slice, 42) // MUST reassign
slice = append(slice, 10)
// Append multiple: slice = append(slice, 1, 2, 3)2. Slice Sharing
A sub-slice shares the backing array with the original. Modifying one affects the other. Use copy() to get an independent copy.
List<Integer> sub = new ArrayList<>(list.subList(1, 3)); // copysub := slice[1:3] // shares backing array!
// Independent copy:
dst := make([]int, len(slice))
copy(dst, slice)3. Map Safe Access
Accessing a missing map key returns the zero value (0 for int), not null. Use the two-value form to distinguish zero from missing.
map.getOrDefault("key", 0)val, ok := m["key"]
if ok {
// key exists, val is the value
} else {
// key missing, val is zero value (0, "", etc.)
}4. make for Pre-allocated Collections
Use make([]T, len, cap) to pre-allocate slices when size is known. Avoids repeated reallocation from append.
List<Integer> list = new ArrayList<>(100); // initialCapacity// Pre-allocated slice
s := make([]int, 0, 100) // len=0, cap=100
for i := 0; i < 100; i++ { s = append(s, i) }
// Pre-filled map
m := make(map[string]int, 100)Common Mistakes
When coming from Java, developers often make these mistakes:
- append() always returns the new slice — must reassign: s = append(s, x)
- Slices share underlying array — modifications affect original; use copy() for independence
- Map access returns zero value for missing key; use two-value form (v, ok) to distinguish
Key Takeaways
- append returns new slice — always reassign: s = append(s, item)
- Sub-slices share memory; copy(dst, src) for independent copies
- Map: two-value form (v, ok) := m[k] distinguishes missing from zero value
- make([]T, len, cap) pre-allocates for performance when size is known