Circular ArrayList (расширение ArrayList)

Таким образом, моя программа нуждается в типе кругового массива ArrayList.

Только круговая вещь об этом должна быть методом get (int index), это оригинал:

    /**
     * Returns the element at the specified position in this list.
     *
     * @param  index index of the element to return
     * @return the element at the specified position in this list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */ 
    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }

Если индекс равен -1, он должен получить элемент с индексом ArrayList.size() - 1, а если index - ArrayList.size(), он должен получить элемент с индексом 0.

Самый простой способ добиться этого, который пришел мне на ум, - это просто расширить ArrayList из пакета java.util и просто переопределить индекс get (int), поэтому он не бросает IndexOutOfBoundsException для двух указанных выше индексов, но меняет их на то, что я хотеть. Он будет генерировать IndexOutOfBoundsException для любого другого индекса, который выходит за рамки.

Однако, поскольку elementData (index) получает доступ к

private transient Object[] elementData;

Я не могу заставить его работать, потому что мой класс не видит его с тех пор, как он закрыт.

Кроме того, я не хочу использовать для этого какие-либо внешние библиотеки, просто потому, что я думаю, что нет таких, которые бы удовлетворяли мои потребности, так как я не хочу реального кругового массива, но только часть его функциональности, остальная часть это регулярный ArrayList.

У меня есть два вопроса:

Как я могу сделать эту работу? Есть ли способ сделать это, не копируя весь класс ArrayList вместе с AbstractCollection, Collection и Iterable в мою программу? Это кажется плохим дизайном даже для меня.

Если я могу как-то заставить его работать, есть ли еще что-нибудь, за чем я должен следить? Если я сделаю изменения, описанные выше, изменит ли поведение класса только то, как я его хочу, или могут возникнуть какие-либо другие нежелательные изменения поведения?

EDIT: Спасибо за ответ, вот что я сделал:

import java.util.ArrayList;

public class CircularArrayList<E> extends ArrayList<E>
{
    private static final long serialVersionUID = 1L;

    public E get(int index)
    {
        if (index == -1)
        {
            index = size()-1;
        }

        else if (index == size())
        {
            index = 0;
        }

        return super.get(index);
    }
}

Он будет обтекать ArrayList, но только одним. Я хочу, чтобы он выдавал исключение, если я пытаюсь получить доступ к любому другому элементу, кроме первого и последнего, с чем-либо, кроме своих обычных индексов ArrayList.

Ответ 1

Вы не можете получить из ArrayList и переопределить метод get (int index) по этим строкам:

@Override
public E get(int index)
{
    if(index < 0)
        index = index + size();

    return super.get(index);
}

Что мне не хватает?

Обратите внимание, что эта реализация не сворачивала бы произвольные индексы в ваш допустимый диапазон индексов, а только позволяла вам правильно адресовать ваш список как с левой, так и с правой стороны (с положительными и отрицательными индексами соответственно, немного как на Python).

Ответ 2

Вы можете расширить класс ArrayList, чтобы изменить функциональность метода get, без необходимости доступа к полю elementData:

public class CircularList<E> extends ArrayList<E> {

    @Override
    public E get(int index) {
        return super.get(index % size());
    }
}

Метод super.get по-прежнему будет выполнять проверки диапазона (но они никогда не сбой).

Вы должны знать, что это может привести к неустойчивым индексам ArrayList. Если размер списка изменится, все индексы вне нормального диапазона изменятся. Например, если у вас есть список ['a','b','c','d','e'], то get(7) вернет c. Если вы затем сделаете add('f'), тогда get(7) неожиданно вернется b, потому что get теперь будет работать по модулю 6 вместо модуля 5.

Ответ 3

То, что вы описали, в основном получает модуль нужного вам индекса и доступ к этому элементу в списке.

Вы можете сделать следующее с композицией над наследованием:

  • Создайте класс оболочки для интерфейса List<T>, позвоните ему сейчас в ListWrapper
    • добавить конструктор, принимающий экземпляр List
    • пусть экземпляр List будет защищен и назовите его wrapped
  • Расширить класс оболочки

Почему все это дерьмо? Это агностика реализации. В один прекрасный день вы можете использовать это удобство для другой реализации. Тогда вам придется дублировать код, и начинается ад. Если вам нужна и третья реализация, а затем добавьте только один крошечный бит новых функций, вы обречены.

С классом-оболочкой между:

  • вы можете иметь все классы, реализующие интерфейс List, чтобы иметь свою собственную функциональность.
  • вы сможете изменить класс оболочки в одном месте.
  • вы сможете добавлять новые функции в одном месте.

Помните, что мы пишем программы, которые должны быть поддерживаемыми!

Класс обертки

public abstract class ListWrapper<T> implements List<T> {
    protected final List<T> wrapped;

    public ListWrapper(List<T> wrapped) {
        this.wrapped = wrapped;
    }

    public T get(int index) {
        return wrapped.get(index);
    }

    //omitting the other wrapper methods, for sake of brevity.
    //Note: you still have to add them.
    // Eclipse: Source menu, Generate Delegate methods does the trick nicely
}

Теперь настоящий новый класс

public class ModList<T> extends ListWrapper<T> {

    public ModList(List<T> list) {
        super(list);
    }

    @Override
    public T get(int index) {
        int listSize = wrapped.size();
        int indexToGet = index % listSize;

        //this might happen to be negative
        indexToGet = (indexToGet < 0) ? indexToGet+listSize : indexToGet;
        return wrapped.get(indexToGet);
    }

}

BEWARE

  • однако это небезопасно для многопоточных сред!
  • будьте осторожны со всеми экземплярами исходного списка - если вы его мутируете, экземпляр ModList тоже будет мутировать.

Ответ 4

Выбранный ответ не обрабатывает случай, когда индекс является отрицательным числом с очень большой величиной, а размер списка мал i.e.

Размер = > 10 Index = > -1000000

Вот реализация, которая должна обрабатывать все размеры и индексы

import java.util.ArrayList;
import java.util.Collection;

/**
 * A list the loops round to the first element when {@link CircularList#get(int)} is called with an
 * index that is greater than the max index of the list and vice versa.
 *
 * @author Stuart Clark
 */
public class CircularList<E> extends ArrayList<E> {

  public CircularList() {
    super();
  }

  public CircularList(int initialCapacity) {
    super(initialCapacity);
  }

  public CircularList(Collection<? extends E> c) {
    super(c);
  }

  @Override
  public E get(int index) {
    if (isEmpty()) {
      throw new IndexOutOfBoundsException("The list is empty");
    }

    while (index < 0) {
      index = size() + index;
    }

    return super.get(index % size());
  }

}

Ответ 5

Кто-нибудь знает это расширение AbstractList: com.sun.appserv.management.util.misc.CircularList<T>. Взгляните на это. Это решение сообщества GlassFish java.net. Он должен быть мощным, потому что он использовался при планировании потоков внутри контейнера GlassFish.