Реализовать аналог Collections.emptyList()

69. Реализовать собственный неизменяемый пустой список

Условие задачи:
Необходимо реализовать аналог Collections.emptyList(), который возвращает неизменяемый пустой список.

Требования:

  • нельзя использовать Collections.emptyList() и List.of();

  • при каждом вызове должен возвращаться один и тот же объект;

  • метод должен сохранять тип элементов списка;

  • операции изменения должны завершаться UnsupportedOperationException.


Спойлеры к решению

Подсказки
💡 Храни единственный экземпляр списка в поле static final.
💡 Один пустой список можно безопасно использовать для любых типов.
💡 Метод должен объявлять собственный параметр типа <T>.
💡 Унаследуй реализацию от AbstractList, чтобы получить стандартное поведение списка.
💡 Метод size() всегда должен возвращать 0.

Решение
public final class MyCollections {

    private static final List<?> EMPTY_LIST = new EmptyList<>();

    private MyCollections() {
    }

    @SuppressWarnings("unchecked")
    public static <T> List<T> emptyList() {
        return (List<T>) EMPTY_LIST;
    }

    private static final class EmptyList<T>
            extends AbstractList<T>
            implements RandomAccess {

        @Override
        public T get(int index) {
            throw new IndexOutOfBoundsException(
                    "Index: " + index + ", size: 0"
            );
        }

        @Override
        public int size() {
            return 0;
        }
    }
}

Пример использования:

List<String> strings = MyCollections.emptyList();
List<Integer> numbers = MyCollections.emptyList();

System.out.println(strings.isEmpty());
System.out.println(strings == (Object) numbers);

Результат:

true
true

Для всех типов возвращается один объект:

private static final List<?> EMPTY_LIST = new EmptyList<>();

Приведение к List<T> безопасно, поскольку список не содержит элементов и не позволяет их добавлять.

Методы изменения, унаследованные от AbstractList, выбрасывают UnsupportedOperationException:

strings.add("value");

Сложность size() и isEmpty()O(1). Новый экземпляр при вызове emptyList() не создаётся.