eclipse-collections
eclipse-collections copied to clipboard
Implement boxing wrappers for primitive collections.
Sometimes, one may want to pass a primitive collection into a method that takes a JCF interface. In cases like this, users must transform the primitive collection into an object collection to proceed.
public static void printAll(List<?> list)
{
System.out.println("list = " + Iterate.makeString(list, ", "));
}
public static void main(String[] args)
{
MutableIntList list = IntLists.mutable.with(1, 2, 3);
printAll(list.collect(Integer::valueOf));
}
One option would be to make our primitive collections implement JCF interfaces. Fastutils does this; for example its IntArrayList
implements List<Integer>
The downside is that it's very easy to accidentally cause boxing by using the primitive collection inside an enhanced for-loop.
A compromise that I believe would be pragmatic would be to implement a boxing wrappers for primitive collections, and a new method boxed(). If it existed, the example above would become:
printAll(list.boxed());
The boxed() method would take O(1) time and space since it returns a wrapper.
To prove the idea, I wrote one wrapper. If we like the idea, we'd need to code generate it for all the primitives, and expand the functionality from just lists to sets and maps.
public class BoxedMutableIntList
extends AbstractMutableList<Integer>
implements MutableList<Integer>, RandomAccess
{
private final MutableIntList delegate;
public BoxedMutableIntList(MutableIntList delegate)
{
this.delegate = Objects.requireNonNull(delegate);
}
@Override
public int size()
{
return this.delegate.size();
}
@Override
public boolean isEmpty()
{
return this.delegate.isEmpty();
}
@Override
public boolean contains(Object o)
{
return o instanceof Integer && this.delegate.contains((Integer) o);
}
@Override
public boolean add(Integer integer)
{
return this.delegate.add(integer.intValue());
}
@Override
public boolean remove(Object o)
{
return o instanceof Integer && this.delegate.remove((Integer) o);
}
@Override
public boolean addAll(int index, Collection<? extends Integer> c)
{
return this.delegate.addAllAtIndex(index, c.stream().mapToInt(Integer::intValue).toArray());
}
@Override
public void clear()
{
this.delegate.clear();
}
@Override
public Integer get(int index)
{
return this.delegate.get(index);
}
@Override
public Integer set(int index, Integer element)
{
return this.delegate.set(index, element.intValue());
}
@Override
public void add(int index, Integer element)
{
this.delegate.addAtIndex(index, element.intValue());
}
@Override
public Integer remove(int index)
{
return this.delegate.removeAtIndex(index);
}
@Override
public int indexOf(Object o)
{
return o instanceof Integer ? this.delegate.indexOf((Integer) o) : -1;
}
@Override
public int lastIndexOf(Object o)
{
return o instanceof Integer ? this.delegate.lastIndexOf((Integer) o) : -1;
}
@Override
public MutableList<Integer> subList(int fromIndex, int toIndex)
{
return this.delegate.subList(fromIndex, toIndex).boxed();
}
}
Plain JCF or EC List?
I was thinking plain JCF when I wrote up the issue. But I'm looking at what it would take to make boxed() return MutableList instead of List, and it looks only marginally more effort.
Updated the example wrapper to implement MutableList. For some reason, I had to implement just one more method, addAll.
If we do this, are we then going to have endless enhancements to optimize the other methods on there? (It's unclear to me why sublist was overridden, for example).
I implemented sublist() just to get the wrapper compiling, by delegating to the primitive sublist and then boxing that. I realized afterwards that it won't work anyway because the implementation of IntArrayList is:
@Override
public MutableIntList subList(int fromIndex, int toIndex)
{
throw new UnsupportedOperationException("subList not yet implemented!");
}
But if someone implements a proper sublist, then sublist(start, end).boxed() could start working.
In the code I pasted, few of the implementations are there for optimization. I didn't start by extending AbstractList or AbstractMutableList so it's possible I could delete a few more methods. I think I can get rid of indexOf and lastIndexOf for example. I'll try to minimize it again and update.
confused... my copy of AbstractMutableList
has this:
@Override
public MutableList<T> subList(int fromIndex, int toIndex)
{
return new SubList<>(this, fromIndex, toIndex);
}
why is that not compiling?
I see what you mean. I had started with List and AbstractList which don't give an implementation of subList(). It's actually easier to implement MutableList than List. Here's the new smaller implementation.
public class BoxedMutableIntList
extends AbstractMutableList<Integer>
implements MutableList<Integer>, RandomAccess
{
private final MutableIntList delegate;
public BoxedMutableIntList(MutableIntList delegate)
{
this.delegate = Objects.requireNonNull(delegate);
}
@Override
public int size()
{
return this.delegate.size();
}
@Override
public boolean add(Integer integer)
{
return this.delegate.add(integer.intValue());
}
@Override
public boolean addAll(int index, Collection<? extends Integer> c)
{
return this.delegate.addAllAtIndex(index, c.stream().mapToInt(Integer::intValue).toArray());
}
@Override
public void clear()
{
this.delegate.clear();
}
@Override
public Integer get(int index)
{
return this.delegate.get(index);
}
@Override
public Integer set(int index, Integer element)
{
return this.delegate.set(index, element.intValue());
}
@Override
public void add(int index, Integer element)
{
this.delegate.addAtIndex(index, element.intValue());
}
@Override
public Integer remove(int index)
{
return this.delegate.removeAtIndex(index);
}
}
That looks good to me, so long as there is a clear comment that says something to effect of "this is for scenarios where optimization is not a consideration and therefore no further methods will be optimized".
I could help out with this.
There is no c.stream().mapToFloat Hence, this method can't be implemented for float:
@Override public boolean addAll(int index, Collection<? extends Float> c) { return this.delegate.addAllAtIndex(index, c.stream().mapToFloat(Float::floatValue).toArray()); }
Any suggestions to avoid creating an intermediate List that will be flattened into array?
Any suggestions to avoid creating an intermediate List that will be flattened into array?
The only efficient way I can think of is to add a bunch of zeros at that index (the same size as the collection), then set the actual values by iterating through the collection. If the index is at the end, adding zeros should be skipped.
As far as I can tell, it's not been brought up: why not two options (i.e. .boxCopy()
and .boxView()
) where one does all of the boxing at once and the other is a wrapper? This allows the former to be used in the case that the collection should be moved into/owned by some consumer, and the latter for when a helper is using the collection sparsely. I'm not familiar with optimizing collections operations on the JVM so please forgive me if I'm missing something.
@s5bug If you really want a boxed collection, we already have a decent way to perform that with collect().
MutableList<Integer> boxed = IntLists.mutable.with(1, 2, 3).collect(each -> each);
Sorry for the delay - please let me know what you think!
This doesn't seem to exist for Map
s, in which my specific use-case is ObjectLongMap<UUID>
. (And this is only for copies and not for views, right?)
That's a really good point about maps - I don't think we have any form of collect() or any other method that will created object Maps.
Once we do support boxed, a pattern like this will be possible.
ObjectLongMap<UUID> map = ...;
MapIterable<UUID, Long> boxed = map.boxed();
MutableMap<UUID, Long> objectMap = Maps.mutable.withMapIterable(boxed);
That's still an extra step. The idea of separate methods for boxed views and copies is a nice one and I'm going to keep it in mind. I'm curious if other collections frameworks have both.