Javanese Online

Списки. Интерфейс List, класс ArrayList, обёртка unmodifiableList

Названия в других языках программирования: динамический массив, вектор.

Ранее мы ознакомились с массивами, но их минус в том, что у них фиксированная длина. У них есть более гибкие собратья — списки. Списки реализуют интерфейс List. Они умеют делать всё то же, что и массивы, только их длина динамическая. Самая популярная реализация списка — это ArrayList.

String[] stringArray = new String[4];
// [null, null, null, null]
// так создаётся массив, который может хранить четыре строки

List<String> stringList = new ArrayList<>();
// []
// так создаётся пустой список строк

При записи в массив обычно устанавливают значение n-го элемента, а при записи в список — добавляют элементы.

stringArray[0] = "firstString";
// установить n-ый элемент массива

stringList.add("firstString");
// добавить элемент в конец списка

Добавляя элементы в список, можно не задумываться о его размере. Так, массив можно сравнить с коробкой, а ArrayList — с менеджером коробок, который сам выберет коробку подходящего размера.

String[] stringArray = new String[4];
System.out.println(Arrays.toString(stringArray));
// [null, null, null, null]
List<String> stringList = new ArrayList<>();
System.out.println(stringList);
// []

stringArray[0] = "firstString";
System.out.println(Arrays.toString(stringArray));
// [firstString, null, null, null]
stringList.add("firstString");
System.out.println(stringList);
// [firstString]

stringArray[1] = "secondString";
System.out.println(Arrays.toString(stringArray));
// [firstString, secondString, null, null]
stringList.add("secondString");
System.out.println(stringList);
// [firstString, secondString]

Декоратор unmodifiableList

Так как экземпляр ArrayList может быть изменён, передавать его другому коду потенциально опасно: чужой код может изменить (мутировать) ArrayList:

System.out.println(me.getPreferredMusicGenres());
// [metal, metalcore, symphonic metal, post-hardcore, progressive metal]
List<String> genres = me.getPreferredMusicGenres();
genres.set(0, "pop");
genres.set(1, "popcore");
genres.set(2, "symphonic pop");
genres.set(3, "post-pop");
genres.set(4, "progressive pop");
System.out.println(me.getPreferredMusicGenres());
// [pop, popcore, symphonic pop, post-pop, progressive pop]

Вместо того чтобы непосредственно возвращать экземпляр ArrayList, лучше предварительно обернуть его в неизменяемую обёртку. Collections.unmodifiableList() оборачивает переданный список в экземпляр класса, который передаёт (проксирует) все операции чтения к исходному списку, но при попытке записи выбрасывает исключение.

class Me {
    private final List<String> preferredMusicGenres;
    Me() {
        List<String> genres = new ArrayList<>();
        genres.add("metal");
        genres.add("metalcore");
        genres.add("symphonic metal");
        genres.add("post-hardcore");
        genres.add("progressive metal");
        this.preferredMusicGenres = genres;
    }
    List<String> getPreferredMusicGenres() {
//        return preferredMusicGenres;
        return Collections.unmodifiableList(preferredMusicGenres);
    }
}

Теперь при попытке выполнить код, изменяющий список, будет выброшено исключение:

Exception in thread "main" java.lang.UnsupportedOperationException
	at java.util.Collections$UnmodifiableList.set(Collections.java:1311)
	at online.javanese.basics.stdlib.lists.ArrayListMutation.main(ArrayListMutation.java:17)

Защитное копирование

Также можно возвращать копию списка (так называемое защитное копирование), но это повышает потребление памяти, т. к. на каждый вызов метода создаётся новая копия списка.

List<String> getPreferredMusicGenres() {
    return new ArrayList<>(preferredMusicGenres);
            // ^ конструктор копирования
            //   (copy constructor)

}

В этом случае сторонний код не сможет испортить список, т. к. будет работать с его копией.

System.out.println(me.getPreferredMusicGenres());
// [metal, metalcore, symphonic metal, post-hardcore, progressive metal]
List<String> genres = me.getPreferredMusicGenres();
genres.set(0, "pop");
genres.set(1, "popcore");
genres.set(2, "symphonic pop");
genres.set(3, "post-pop");
genres.set(4, "progressive pop");
System.out.println(genres);
// [pop, popcore, symphonic pop, post-pop, progressive pop]
System.out.println(me.getPreferredMusicGenres());
// [metal, metalcore, symphonic metal, post-hardcore, progressive metal]

Задание ёмкости списка заранее

Также есть важная оптимизация. Если заранее известен размер будущего списка, можно передать в его конструктор количество элементов: new ArrayList(5) создаст пустой ArrayList, который выделит память под пять элементов.

List<String> genres = new ArrayList<>(5);
genres.add("metal");
genres.add("metalcore");
genres.add("symphonic metal");
genres.add("post-hardcore");
genres.add("progressive metal");

Если же не указать размер (new ArrayList()), память выделится под десять элементов. Если элементов будет меньше, оставшаяся память выделена зря. Если же их больше, ArrayList будет расти, т. е. выделять новую память.

Комментарии к уроку

Сообщить об ошибке

Javanese.Online в GitHub

Чаты и каналы в Telegram

RSS-лента