Apache Commons solution : ListOrderedMap
Since the JDK's LinkedHashMap
ensures only insertion order retrieval, in case you we want to insert at an index, we can use alternatively Apache Commons' ListOrderedMap
. It does it as it sounds - by having a list to maintain the order of insertion with the corresponding index and a normal map to insert as we generally do. Here is what the docs say:
public class ListOrderedMap<K,V>
extends AbstractMapDecorator<K,V>
implements OrderedMap<K,V>, Serializable
Decorates a Map
to ensure that the order of addition is retained
using a List to maintain order.
The order will be used via the iterators and toArray
methods on the
views. The order is also returned by the MapIterator
. The
orderedMapIterator()
method accesses an iterator that can iterate
both forwards and backwards through the map. In addition,
non-interface methods are provided to access the map by index.
If an object is added to the Map for a second time, it will remain in
the original position in the iteration.
Note that ListOrderedMap
is not synchronized and is not thread-safe.
If you wish to use this map from multiple threads concurrently, you
must use appropriate synchronization. The simplest approach is to wrap
this map using Collections.synchronizedMap(Map)
. This class may
throw exceptions when accessed by concurrent threads without
synchronization.
Note that ListOrderedMap
doesn't work with IdentityHashMap
,
CaseInsensitiveMap
, or similar maps that violate the general
contract of Map
. The ListOrderedMap
(or, more precisely, the
underlying List
) is relying on equals()
. This is fine, as long as
the decorated Map
is also based on equals()
, and hashCode()
,
which IdentityHashMap
, and CaseInsensitiveMap
don't: The former
uses ==
, and the latter uses equals()
on a lower-cased key.
Here is its implementation for adding to a position:
/**
428 * Puts a key-value mapping into the map at the specified index.
429 * <p>
430 * If the map already contains the key, then the original mapping
431 * is removed and the new mapping added at the specified index.
432 * The remove may change the effect of the index. The index is
433 * always calculated relative to the original state of the map.
434 * <p>
435 * Thus the steps are: (1) remove the existing key-value mapping,
436 * then (2) insert the new key-value mapping at the position it
437 * would have been inserted had the remove not occurred.
438 *
439 * @param index the index at which the mapping should be inserted
440 * @param key the key
441 * @param value the value
442 * @return the value previously mapped to the key
443 * @throws IndexOutOfBoundsException if the index is out of range [0, size]
444 * @since 3.2
445 */
446 public V put(int index, final K key, final V value) {
447 if (index < 0 || index > insertOrder.size()) {
448 throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + insertOrder.size());
449 }
450
451 final Map<K, V> m = decorated();
452 if (m.containsKey(key)) {
453 final V result = m.remove(key);
454 final int pos = insertOrder.indexOf(key);
455 insertOrder.remove(pos);
456 if (pos < index) {
457 index--;
458 }
459 insertOrder.add(index, key);
460 m.put(key, value);
461 return result;
462 }
463 insertOrder.add(index, key);
464 m.put(key, value);
465 return null;
466 }