问题
So my program has a need of a type of circular ArrayList.
Only circular thing about it has to be the get(int index) method, this is the original:
/**
* 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);
}
If index is -1 it should get the element with index ArrayList.size()-1 and if index is ArrayList.size(), it should get the element with index 0.
Simplest way of achieveing this which came to my mind is simply extending ArrayList from the java.util package and just overriding the get(int index) so it does not throw IndexOutOfBoundsException for the two indexes above, but change them to what I want. It would throw IndexOutOfBoundsException for any other index that is out of bounds.
However, since elementData(index) access a
private transient Object[] elementData;
I cannot make it work, because my class doesn't see it since it's private.
Also, I don't want to use any external libraries for this, simply because I think there are none that suit my needs, since I don't want a real circularArray, but only a part of it's functionality, rest of it being of the regular ArrayList.
So I have two questions:
How can I make this work? Is there a way to do it without copying the whole ArrayList class along with AbstractCollection, Collection and Iterable into my program? That seems like bad design even to me.
If I can somehow make it work, is there anything else I should watch for? If I make the changes described above, would that change the behaviour of the class only the way I want it to, or could there be any other undesired behaviour changes?
EDIT: Thanks for the answer, here's what I've done:
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);
}
}
It will wrap around the ArrayList, but only by one. I want it to throw an exception if I try to access any other element but the first and the last with anything except their regular ArrayList indexes.
回答1:
Can't you derive from ArrayList and override the the get(int index) method along those lines:
@Override
public E get(int index)
{
if(index < 0)
index = index + size();
return super.get(index);
}
What am I missing?
Note that this implementation would not fold arbitrary indices into your valid index range but only allow you to properly address your list from both the left and right sides (with positive and negative indices respectively, a bit like in Python).
回答2:
You can extend the ArrayList class to change the functionality of the get
method, without the need to access the elementData
field:
public class CircularList<E> extends ArrayList<E> {
@Override
public E get(int index) {
return super.get(index % size());
}
}
The super.get
method will still perform the range checks (but those will never fail).
You should be aware that doing this can give the ArrayList unstable indices. If the size of the list changes, then all indices outside of the normal range will change. For instance, if you have a list ['a','b','c','d','e']
, then get(7)
will return c
. If you then do add('f')
, then get(7)
will suddenly return b
, because get
will now be working modulo 6 instead of modulo 5.
回答3:
What you described is basically getting the modulus of the index you want, and accessing that element in a list.
You could do the following with composition over inheritance:
- Create a wrapper class for the interface
List<T>
, let's call it ListWrapper now- add a constructor accepting instance of List
- let the List instance be protected, and name it to
wrapped
- Extend the wrapper class
Why do all this crap? This is implementation agnostic. One day, you might want to use this convenience on another implementation. Then you'll have to duplicate code, and hell begins. If you need a 3rd implementation too, and then add just one tiny bit of new functionality, you are doomed.
With a wrapper class in between:
- you can have all classes implementing the List interface to have your own functinality
- you'll be able to change the wrapper class in one place
- you'll be able to add new functionality in one place.
Remember, we are writing programs that will have to be maintainable!
Wrapper class
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
}
Now the real new class
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
- this however is not safe for multithreaded environments!
- be careful about all the instances of the original list - if you mutate that, the ModList instance will mutate too
回答4:
The chosen answer doesn't handle the case where the index is a negative number with a very large magnitude and the size of the list is small i.e.
Size => 10 Index => -1000000
Here is an implementation that should handle all sizes and indexes
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:
Does anyone know this AbstractList extension : com.sun.appserv.management.util.misc.CircularList<T>
. Take a look at it. It's GlassFish java.net community solution. It should be powerful because it's used in Thread Scheduling inside GlassFish Container.
来源:https://stackoverflow.com/questions/18659792/circular-arraylist-extending-arraylist