Разбираем: array, slice, map, zero value - в Go vs Java | Types - Language
Серия: Go для Java-разработчиков
Эта статья открывает серию материалов о языке Go для разработчиков, которые уже хорошо знакомы с Java. Мы будем сравнивать подходы двух языков, чтобы быстрее понять, как устроена модель данных Go и почему она выглядит именно так.
Зачем вообще разбирать базовые структуры
В Java почти все коллекции — это классы из стандартной библиотеки. Вы создаёте объект ArrayList, HashMap, HashSet и работаете с ними.
В Go всё устроено немного иначе. Многие структуры данных встроены прямо в язык.
- array — фиксированный массив
- slice — динамическая структура поверх массива
- map — хеш-таблица
- zero value — встроенное значение по умолчанию
Именно поэтому важно понять, как они связаны с памятью. Go делает многие вещи проще, но иногда — менее очевидно для Java-разработчика.
Array в Go
Array в Go — это фиксированный массив. Размер является частью типа.
Это значит, что [3]int и [5]int — разные типы.
Пример
// объявляем массив из 3 элементов
var nums [3]int
// записываем значения
nums[0] = 10
nums[1] = 20
nums[2] = 30
// вывод
fmt.Println(nums)
Java эквивалент:
// массив фиксированного размера
int[] nums = new int[3];
nums[0] = 10;
nums[1] = 20;
nums[2] = 30;
System.out.println(Arrays.toString(nums));
На первый взгляд — всё одинаково. Но в Go массивы используются значительно реже.
В реальном коде Go почти всегда используют slice, а не array. Array чаще применяется как внутренний механизм хранения данных.
Slice — основной контейнер Go
Slice — это динамическая структура, которая работает поверх массива.
Фактически slice — это небольшой дескриптор, который содержит три вещи:
- указатель на массив
- длину
- capacity (ёмкость)
Пример slice
// создаем slice
var nums []int
// добавляем элемент
nums = append(nums, 1)
// добавляем ещё
nums = append(nums, 2, 3)
fmt.Println(nums)
Java эквивалент:
// динамический список
List<Integer> nums = new ArrayList<>();
// добавляем элементы
nums.add(1);
nums.add(2);
nums.add(3);
System.out.println(nums);
Поведение похоже на ArrayList, но реализовано иначе.
Slice Backing Array
Самая важная концепция — backing array.
Slice хранит ссылку на массив, в котором реально лежат данные.
Схематически это выглядит так:
Slice
├─ pointer ────────────┐
├─ length │
└─ capacity │
▼
Backing Array
[10][20][30][40][50]
Если сделать новый slice, он может указывать на тот же массив.
Пример
// исходный массив
arr := [5]int{1,2,3,4,5}
// создаем slice
s1 := arr[1:4]
fmt.Println(s1)
Java аналог (условный)
// в Java подобного механизма нет напрямую
int[] arr = {1,2,3,4,5};
// обычно копируют часть массива
int[] slice = Arrays.copyOfRange(arr, 1, 4);
System.out.println(Arrays.toString(slice));
В Java чаще происходит копирование, а в Go — совместное использование памяти.
Несколько slice могут ссылаться на один backing array. Изменение данных через один slice изменит данные во всех.
Что происходит при append
Если capacity позволяет, новые элементы добавляются в тот же массив.
Если места не хватает — Go создаёт новый массив и копирует данные.
append() Slice ↓ capacity хватает? │ ├─ да → запись в тот же массив │ └─ нет → новый массив + копирование
Это похоже на внутреннюю работу ArrayList, который также увеличивает внутренний массив.
Map в Go
Map — это встроенная хеш-таблица.
Она похожа на HashMap из Java, но является частью языка.
Пример
// создаем map
ages := make(map[string]int)
// добавляем значения
ages["Alice"] = 30
ages["Bob"] = 25
fmt.Println(ages["Alice"])
Java эквивалент:
// HashMap
Map<String, Integer> ages = new HashMap<>();
ages.put("Alice", 30);
ages.put("Bob", 25);
System.out.println(ages.get("Alice"));
Zero Value — важная особенность Go
В Go каждая переменная автоматически получает значение по умолчанию.
Это называется zero value.
| Тип | Zero value | Комментарий |
|---|---|---|
| int | 0 | числовой ноль |
| string | "" | пустая строка |
| bool | false | логическое значение |
| slice | nil | но append всё равно работает |
| map | nil | нужно создать через make |
Пример slice без инициализации
// nil slice
var nums []int
// append работает
nums = append(nums, 1)
fmt.Println(nums)
Java аналог
// в Java переменная будет null
List<Integer> nums = null;
// nums.add(1) вызовет NullPointerException
Zero value позволяет писать меньше защитного кода. Многие структуры можно использовать сразу после объявления.
Сравнение Go и Java
| Концепция | Go | Java | Комментарий |
|---|---|---|---|
| Массив | array | array | похожи |
| Динамический список | slice | ArrayList | slice встроен в язык |
| Хеш-таблица | map | HashMap | map часть языка |
| Значение по умолчанию | zero value | null / 0 | в Go меньше ошибок null |
| Управление памятью | slice + backing array | внутренний массив | похоже на ArrayList |
Constants / Константы
В Go константы объявляются через const и хранят неизменяемые значения. Можно создавать одиночные или групповые константы. Отличие от Java — типы могут быть неявными и поддерживается iota для автоматической генерации последовательностей.
Пример одиночной константы
// Go
const Pi = 3.14159
func main() {
fmt.Println(Pi)
}
Java эквивалент:
// Java
class Main {
static final double PI = 3.14159;
public static void main(String[] args) {
System.out.println(PI);
}
}
Пример групповых констант с iota
// Go
const (
Sunday = iota
Monday
Tuesday
)
func main() {
fmt.Println(Sunday, Monday, Tuesday) // 0 1 2
}
Java эквивалент через enum:
// Java
enum Day { SUNDAY, MONDAY, TUESDAY }
public class Main {
public static void main(String[] args) {
System.out.println(Day.SUNDAY.ordinal()); // 0
System.out.println(Day.MONDAY.ordinal()); // 1
System.out.println(Day.TUESDAY.ordinal()); // 2
}
}
Совет: используйте iota для последовательных числовых констант. Enum в Java обеспечивает похожую функциональность, но более строго типизирован.
Практический итог
Для Java-разработчика важнее всего понять одну вещь:
в Go контейнеры — это не столько классы, сколько языковые конструкции.
Главные отличия:
- slice заменяет большинство случаев использования ArrayList
- map — встроенная хеш-таблица
- zero value снижает количество ошибок
- несколько slice могут разделять одну память
Самая частая ошибка начинающих Go-разработчиков — забывать, что slice может разделять backing array. Это может приводить к неожиданным изменениям данных.
Практическое правило:
- используйте slice вместо array
- помните про append и capacity
- используйте map для ассоциативных структур
- помните, что zero value часто уже готов к использованию
Когда эти принципы становятся привычными, код на Go начинает ощущаться значительно проще, чем код на Java с большим количеством объектов-обёрток.
Галерея
Полезные статьи:
Новые статьи: