Коллекции ·
‹ Previous Next ›
⏱ 5 min read Modified: 2026-10-25

Backed Collections и Array-backed списки в Java

Получите список из массива через List<String> list = Arrays.asList(array) и вызовите list.set(0, "яблоко") — изменится и сам массив: array[0] теперь тоже "яблоко". А следующая строка list.add("груша") упадёт с java.lang.UnsupportedOperationException. Оба эффекта объясняются одним: такой список не хранит своих данных, он лишь «окно» в массив.

Backed collection (коллекция-представление, view) — это объект-коллекция, у которого нет собственного хранилища: все операции чтения и записи он перенаправляет в исходную коллекцию или массив. Поэтому изменения в источнике сразу видны через представление, а изменения через представление попадают в источник. Array-backed список — частный случай, когда источник — массив; его возвращает Arrays.asList(). Представления создаются за O(1), ничего не копируя.

Какие методы возвращают представления

В Java представление (view) возвращают Arrays.asList(), List.subList(), методы keySet(), values() и entrySet() у Map, диапазонные методы отсортированных коллекций (headSet(), subMap() и другие), descendingMap(), reversed() (Java 21) и обёртки класса Collections. Каждое представление разрешает свой набор операций.

Метод Представление чего Что можно делать Граничный случай / ошибка
Arrays.asList(T... a) Массива get(), set(), сортировка add(), remove(int) — UnsupportedOperationException: размер массива не меняется
list.subList(from, to) Части списка, индексы [from, to) Всё, что разрешает исходный список, включая add() и clear() add()/remove() в исходном списке — при следующем обращении к подсписку ConcurrentModificationException
map.keySet(), values(), entrySet() Ключей, значений, пар карты Перебор, удаление (пара исчезает из карты), entry.setValue() add() — UnsupportedOperationException
headSet(), subSet(), tailSet() Диапазона элементов TreeSet Добавление и удаление внутри диапазона Элемент вне диапазона — IllegalArgumentException: key out of range
headMap(), subMap(), tailMap() Диапазона ключей TreeMap put() и remove() внутри диапазона Ключ вне диапазона — IllegalArgumentException: key out of range
descendingMap(), descendingSet(), list.reversed() (Java 21) Той же коллекции в обратном порядке То же, что исходная коллекция Копии нет: изменения через представление попадают в оригинал
Collections.unmodifiableList(list) и другие обёртки Исходной коллекции Только чтение Изменения, сделанные в обход обёртки, через неё видны

Представления Map подробно разобраны в разделе Map.Entry, keySet(), values() и entrySet(), диапазоны множеств — в уроке SortedSet и TreeSet, а обёртки unmodifiableList() и synchronizedList() — в разделе Обёртки класса Collections. Ниже — два классических примера backed-коллекций и подсписки.

Пример backed-коллекции: subMap() у TreeMap

Метод subMap(fromKey, toKey) интерфейса SortedMap возвращает представление ключей от fromKey включительно до toKey не включительно. Записи, добавленные в исходную карту внутри этого диапазона, появляются в представлении, а записи, добавленные через представление, попадают в исходную карту.

import java.util.SortedMap;
import java.util.TreeMap;

public class BackedCollections {
    public static void main(String[] args) {
        SortedMap<String, String> map = new TreeMap<>();
        map.put("а", "арбуз");
        map.put("в", "вишня");
        map.put("д", "дыня");

        SortedMap<String, String> subMap = map.subMap("б", "ж");
        System.out.println(map + " " + subMap);

        map.put("б", "брусника");
        subMap.put("г", "груша");
        map.put("я", "яблоко");
        //   subMap.put("с", "слива");
        System.out.println(map + " " + subMap);
    }
}

Вывод:

{а=арбуз, в=вишня, д=дыня} {в=вишня, д=дыня}
{а=арбуз, б=брусника, в=вишня, г=груша, д=дыня, я=яблоко} {б=брусника, в=вишня, г=груша, д=дыня}

Ключ «б» добавлен в исходную карту, но виден в subMap, потому что попадает в диапазон от «б» до «ж». «г» добавлен через subMap и оказался в map. «я» есть только в исходной карте: он за границей диапазона. Если раскомментировать строку subMap.put("с", "слива"), программа завершится исключением:

Exception in thread "main" java.lang.IllegalArgumentException: key out of range

Что выведет subMap.put("ё", "ёлка")? Тоже IllegalArgumentException, хотя по алфавиту «ё» стоит перед «ж». Строки сравниваются по кодам символов: у «ж» код U+0436, а у «ё» — U+0451, то есть «ё» для TreeMap идёт после «я» (U+044F). Методы headMap(), tailMap() и версии с флагами inclusive работают по тем же правилам — см. разделы headMap, subMap и tailMap — живые представления и Диапазоны в NavigableMap.

Array-backed список: Arrays.asList()

Метод Arrays.asList(array) возвращает список фиксированного размера, который хранит ссылку на переданный массив, а не его копию. Замена элемента через set() меняет массив, запись в массив видна в списке, а методы, меняющие размер (add(), remove(), clear()), бросают UnsupportedOperationException.

import java.util.Arrays;
import java.util.List;

public class BackedArrayCollection {
    public static void main(String[] args) {
        String[] array = {"арбуз", "вишня", "дыня"};
        List<String> list = Arrays.asList(array);

        System.out.println("Список: " + list);
        System.out.println("Массив: " + Arrays.toString(array));

        list.set(0, "яблоко");
        array[1] = "брусника";

        System.out.println("Список: " + list);
        System.out.println("Массив: " + Arrays.toString(array));
    }
}

Вывод:

Список: [арбуз, вишня, дыня]
Массив: [арбуз, вишня, дыня]
Список: [яблоко, брусника, дыня]
Массив: [яблоко, брусника, дыня]

«яблоко» записано через список, «брусника» — напрямую в массив, но оба изменения видны и там, и там: это одни и те же данные. Какие операции поддерживает такой список:

Операция Результат Почему
get(i), size(), contains(), indexOf() Работают Читают массив
set(i, e), list.sort(...), Collections.shuffle(list) Работают и меняют массив Размер не меняется, элементы переставляются на месте
add(e), add(i, e), remove(int), addAll() UnsupportedOperationException Длину массива изменить нельзя
remove(Object), removeIf(), clear() UnsupportedOperationException, если есть что удалять Для отсутствующего элемента remove("груша") просто вернёт false, а clear() пустого списка ничего не сделает
Элемент null Разрешён В отличие от List.of(), Arrays.asList("a", null) допустим

Что выведет этот код?

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class AsListSortExample {
    public static void main(String[] args) {
        String[] array = {"дыня", "арбуз", "вишня"};
        List<String> list = Arrays.asList(array);

        list.sort(null);
        System.out.println(Arrays.toString(array));
        System.out.println(list.getClass());
        System.out.println(list instanceof ArrayList);

        list.add("груша");
    }
}

Вывод:

[арбуз, вишня, дыня]
class java.util.Arrays$ArrayList
false
Exception in thread "main" java.lang.UnsupportedOperationException

Сортировка списка отсортировала и массив. Класс списка называется Arrays$ArrayList — это закрытый вложенный класс Arrays, у которого нет методов, меняющих размер, а не привычный java.util.ArrayList. Из-за совпадения имён в стектрейсе ошибку часто ищут не там.

Массив примитивов не превращается в список чисел

Arrays.asList(new int[]{1, 2, 3}) возвращает List<int[]> из одного элемента — самого массива: обобщённый параметр T не может быть примитивом, поэтому весь int[] становится одним аргументом varargs. Запись List<Integer> list = Arrays.asList(new int[]{1, 2, 3}) не скомпилируется. С Integer[] или перечислением значений Arrays.asList(1, 2, 3) всё работает как ожидается.

Метод subList(): представление части списка

Метод list.subList(from, to) возвращает представление элементов с индекса from включительно до to не включительно. Через подсписок можно читать, заменять, добавлять и удалять элементы — всё сразу отражается в исходном списке. Но если размер исходного списка изменить в обход подсписка, подсписок становится недействительным.

import java.util.ArrayList;
import java.util.List;

public class SubListExample {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>(List.of("a", "b", "c", "d", "e"));
        List<String> sub = list.subList(1, 4);
        System.out.println(sub);

        sub.set(0, "B");
        System.out.println(list);

        sub.clear();
        System.out.println(list + " " + sub);

        list.add("f");
        System.out.println(sub);
    }
}

Вывод:

[b, c, d]
[a, B, c, d, e]
[a, e] []
Exception in thread "main" java.util.ConcurrentModificationException

Строка sub.clear() удалила из исходного списка элементы с индексами 1–3 — это стандартный способ удалить диапазон: list.subList(from, to).clear(). После list.add("f") подсписок «знает», что исходный список изменился структурно, и на любое обращение (toString(), size(), get()) отвечает ConcurrentModificationException. Замена через list.set() структурным изменением не считается и подсписок не ломает. Как работает этот fail-fast механизм, объясняет урок Интерфейс Iterator.

Неверные границы проверяются сразу: для списка из пяти элементов subList(3, 1) бросит IllegalArgumentException: fromIndex(3) > toIndex(1), а subList(0, 10) — IndexOutOfBoundsException: toIndex = 10. У списка из Arrays.asList() подсписок тоже фиксированного размера: subList(0, 2).clear() бросит UnsupportedOperationException.

Представление или копия

Список из Arrays.asList() — представление массива, а List.of(array), List.copyOf() и new ArrayList<>(...) создают копию: после изменения исходного массива они продолжают хранить старые значения. Неизменяемость и связь с источником — разные свойства: List.of() неизменяем, но не является представлением.

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class ViewVsCopyExample {
    public static void main(String[] args) {
        String[] array = {"арбуз", "вишня"};

        List<String> view = Arrays.asList(array);                    // представление
        List<String> copy = new ArrayList<>(Arrays.asList(array));   // изменяемая копия
        List<String> frozen = List.of(array);                        // неизменяемая копия, Java 9
        List<String> frozenCopy = List.copyOf(view);                 // неизменяемая копия, Java 10

        array[0] = "яблоко";

        System.out.println("Arrays.asList:  " + view);
        System.out.println("new ArrayList:  " + copy);
        System.out.println("List.of:        " + frozen);
        System.out.println("List.copyOf:    " + frozenCopy);
    }
}

Вывод:

Arrays.asList:  [яблоко, вишня]
new ArrayList:  [арбуз, вишня]
List.of:        [арбуз, вишня]
List.copyOf:    [арбуз, вишня]

Изменение массива увидел только view. Независимую копию любого представления делают так же: new ArrayList<>(list.subList(1, 4)), new TreeMap<>(map.subMap("б", "ж")), new HashSet<>(map.keySet()). Обратная ситуация с Collections.unmodifiableList(list): изменить список через обёртку нельзя, но это всё равно представление, и изменения исходного list через неё видны. Чем ещё различаются способы создать неизменяемый список (поддержка null, версии Java), сравнивается в разделе Неизменяемые коллекции.

На чём чаще всего ошибаются

Почти все ошибки с backed-коллекциями возникают из-за того, что представление принимают за самостоятельную коллекцию: ждут, что в него можно добавлять элементы, что оно не изменится вместе с источником или что источник можно менять как угодно. Типичные промахи и исправления:

Что сделали Что произойдёт Как исправить
Arrays.asList(...).add(e) или remove(i) UnsupportedOperationException new ArrayList<>(Arrays.asList(...))
Передали int[] в Arrays.asList() Список List<int[]> из одного элемента Integer[] или Arrays.stream(ints).boxed().toList()
Изменили список через set(), не ожидая, что изменится массив Исходный массив тоже изменён Работать с копией: new ArrayList<>(Arrays.asList(array))
Сохранили subList(), затем вызвали add()/remove() у исходного списка ConcurrentModificationException при следующем обращении к подсписку Менять данные через подсписок или сразу скопировать его в new ArrayList<>(...)
put() в subMap()/headMap() ключа вне диапазона IllegalArgumentException: key out of range Добавлять в исходную карту
Вернули из метода Collections.unmodifiableList(list) как «снимок» Последующие изменения list видны вызывающему коду List.copyOf(list)

Часто задаваемые вопросы

Чем Arrays.asList() отличается от List.of()?

Arrays.asList() — представление массива фиксированного размера: set() разрешён и меняет массив, null допустим. List.of() (Java 9) — неизменяемая копия: set() бросает UnsupportedOperationException, null — NullPointerException, изменения исходного массива не видны. Пример с выводом — в разделе Представление или копия.

Как превратить массив int[] в List<Integer>?

Arrays.asList() здесь не поможет. Используйте стрим: Arrays.stream(ints).boxed().toList() (Java 16, неизменяемый список) или Arrays.stream(ints).boxed().collect(Collectors.toList()) для изменяемого. Результат — копия: связи с исходным массивом у него нет.

Может ли subList() привести к утечке памяти?

Да. Подсписок хранит ссылку на весь исходный список, поэтому, если сохранить в поле bigList.subList(0, 10) от списка на миллион элементов, сборщик мусора не освободит ни один из них. Когда нужен долгоживущий фрагмент, копируйте его: new ArrayList<>(bigList.subList(0, 10)) или List.copyOf(...).

Зачем нужны представления, если можно скопировать данные?

Представление создаётся за O(1) и не занимает памяти под элементы, а изменения через него сразу попадают в источник. Поэтому list.subList(a, b).clear() удаляет диапазон за одну операцию, map.keySet().removeIf(...) удаляет пары из карты, а Arrays.asList() позволяет передать массив в методы, принимающие List, без копирования. Копия нужна, когда данные должны жить независимо от источника.

Video Explanation

Prefer video format? Watch this lesson with examples and explanations.

Comments

Please log in or register to have a possibility to add comment.