I am trying to create a user interface with a HashMap. Users could change values and change the name of the keys without disturbing the order of the keys. I searched and found the LinkedHashMap. Which kept the order of the keys in most cases. But when I remove a key and add it back after renaming it, it always adds it to the end. So I've overridden LinkedHashMap class and added a changeKeyName() function.
Now it works (in my case) but I was wondering if it could be improved and made foolproof. I only overridden the functions I was using. What other functions have to be overridden in order make it complete?
Thanks in advance.
Here is the code:
private static class OrderedHashMap<K, V> extends LinkedHashMap<K, V> {
ArrayList<K> keys = new ArrayList<K>();
#Override
public V put(K key, V value) {
if (!keys.contains(key))
keys.add(key);
return super.put(key, value);
}
#Override
public V remove(Object key) {
keys.remove(key);
return super.remove(key);
}
#Override
public Set<K> keySet() {
LinkedHashSet<K> keys = new LinkedHashSet<K>();
for (K key : this.keys) {
keys.add(key);
}
return keys;
}
public void changeKeyName(K oldKeyName, K newKeyName) {
int index = keys.indexOf(oldKeyName);
keys.add(index, newKeyName);
keys.remove(keys.get(index + 1));
V value = super.get(oldKeyName);
super.remove(oldKeyName);
super.put(newKeyName, value);
}
#Override
public Set<Map.Entry<K, V>> entrySet() {
final OrderedHashMap<K, V> copy = this;
LinkedHashSet<Map.Entry<K, V>> keys = new LinkedHashSet<Map.Entry<K, V>>();
for (final K key : this.keys) {
final V value = super.get(key);
keys.add(new Map.Entry<K, V>() {
#Override
public K getKey() {
return key;
}
#Override
public V getValue() {
return value;
}
#Override
public V setValue(V value) {
return copy.put(getKey(), value);
}
});
}
return keys;
}
}
EDIT: I think the why wasn't clear enough. Let's say we added the keys below.
{"key1":"value1"},
{"key2":"value2"},
{"key3":"value3"},
{"key4":"value4"}
And for example I want to change the key name of the "key2". But as this is also a user interface, order of the keys should stay the same.
I made some research and I found out that apart from removing the key and re-puting the new key name with the same value, nothing could be done. So if we do that and change "key2" to "key2a":
{"key1":"value1"},
{"key3":"value3"},
{"key4":"value4"},
{"key2a":"value2"}
And what I want is this:
{"key1":"value1"},
{"key2a":"value2"},
{"key3":"value3"},
{"key4":"value4"}
So I just kept the keys in a ArrayList and returned them when entrySet() and keySet() methods are called.
Have you considered simply using the TreeMap class instead of a custom subclass of LinkedHashMap? It will maintain order if you implement the Comparable interface on the keys.
If you want to be able to change keys without affecting the hashing function in the collection where the value is stored try a custom class such as;
private class VariableKeyMap {
private LinkedHashSet<K, V> myCollection = new LinkedHashSet<K, V>();
private HashMap<int, K> aliases = new HashMap<int, K>();
int id = 0;
public void addEntry(K key, V value) {
id += 1;
aliases.put(K, id);
myCollection.put(id, V);
}
public V getValue(K key) {
return myCollection.get(aliases.get(key));
}
...
}
The you can update your key alias without affecting where the value is actually stored;
public void changeKey(K oldKey, K newKey) {
int currentId = aliases.get(oldKey);
aliases.remove(oldKey);
aliases.put(newKey, currentId);
}
I want to create a subclass of class of java.util.TreeMap, to allow me to add an increment method:
public class CustomTreeMap<K, V> extends TreeMap<K, V> {
public void increment(Integer key) {
Integer intValue;
if (this.containsKey(key)) {
Object value = this.get(key);
if (!(value instanceof Integer)) {
// Fail gracefully
return;
} else {
intValue = (Integer) value;
intValue++;
}
} else {
intValue = 1;
}
this.put(key, intValue); // put(Integer, Integer) cannot be applied in TreeMap
}
}
Android Studio 1.0.2 first proposes put(K Key, V Value) for autocompletion, and later warns that:
put(K, V) cannot be applied in TreeMap to (java.lang.integer, java.lang.integer)
What is it that I am doing wrong?
See here for the solution I adopted.
If you want to create your custom treemap to handle Integers exclusively, you should make it extend TreeMap<K, Integer>, not the generic type V:
public class CustomTreeMap<K> extends TreeMap<K, Integer> {
...
}
This way you don't need the instanceof check later.
If your key also needs to be an Integer, declare no generic types instead:
public class CustomTreeMap extends TreeMap<Integer, Integer> {
...
}
If it should be Integer then use Integer:
public class CustomTreeMap<K> extends TreeMap<K, Integer> {
public void increment(K key) {
Integer intValue;
if (this.containsKey(key)) {
Object value = this.get(key);
if (!(value instanceof Integer)) {
// Fail gracefully
return;
} else {
intValue = (Integer) value;
intValue++;
}
} else {
intValue = 1;
}
this.put(key, intValue); // put(Integer, Integer) cannot be applied in TreeMap
}
}
I've implemented NavigableMap a few times, but it always seems like a bit more work than it should be. While it's a very large interface, things like java.util.AbstractMap and Guava's ForwardingNavigableMap provide much of the boilerplate. However, neither seems to help with the core implementation of subMap/headMap/tailMap (i.e. aside from the overloading for specifying inclusivity flags). For instance, it seems like firstEntry of a sub/tail map could just call ceilingEntry(minKey) or higherEntry(minKey), depending on whether minKey is inclusive. Does Guava or something else provide an easy way to implement these generically? Alternatively, is there a reason I'm overlooking that such an implementation is not practical?
Guava doesn't currently expose AbstractNavigableMap, but I'm not sure even that would help you significantly. TBH, I don't see where it'd buy you much: firstEntry can always be defined as Iterables.getFirst(entrySet().iterator(), null), whether you're a complete map or a submap or whatever. Guava could conceivably provide a complete implementation of subMap and friends that iterated via higherEntry each time, but that's rarely what you want, compared to a more efficient handwritten iterator implementation.
I think it's possible to implement what I'm looking for, but there doesn't seem to be an existing framework for it. Particularly, I want the NavigableMap interface implemented in terms of get and remove for an arbitrary key and navigation in terms of first, last, next, previous. I'm guessing that part of the reason is that I'm representing an external service interaction rather than an internal data structure. The use case is less common and the performance expectations are different.
The following code is my draft implementation. It's only somewhat tested and is intended as a proof of concept. It uses Guava's Iterators, Ordering, and ForwardingNavigableMap, along with nullness annotations from the Checker Framework.
/**
* Abstract base implementation of {#link NavigableMap} that provides all but
* the following methods:
*
* <ul>
* <li>{#link Map#containsKey(Object)}</li>
* <li>{#link Map#get(Object)}</li>
* <li>{#link Map#remove(Object)}</li>
* <li>{#link SortedMap#comparator()}</li>
* <li>{#link NavigableMap#lowerEntry(Object)}</li>
* <li>{#link NavigableMap#floorEntry(Object)}</li>
* <li>{#link NavigableMap#ceilingEntry(Object)}</li>
* <li>{#link NavigableMap#higherEntry(Object)}</li>
* <li>{#link NavigableMap#firstEntry()}</li>
* <li>{#link NavigableMap#lastEntry()}</li>
* </ul>
*
* Note that {#link Collection#size()} is implemented in terms of iteration
* using {#code firstEntry} and {#code higherEntry}, and therefore executes in
* time proportional to the map size. Subclasses may wish to provide a more
* efficient implementation.
*
* #param <K> the type of keys maintained by this map
* #param <V> the type of mapped values
*/
public abstract class AbstractNavigableMap<K, V> extends AbstractMap<K, V> implements
NavigableMap<K, V> {
#Override
public abstract boolean containsKey(Object key);
#Override
public abstract V get(Object key);
#Override
public abstract V remove(Object key);
#Override
public K firstKey() {
return firstEntry().getKey();
}
#Override
public K lastKey() {
return lastEntry().getKey();
}
#Override
public K lowerKey(K key) {
return lowerEntry(key).getKey();
}
#Override
public K floorKey(K key) {
return floorEntry(key).getKey();
}
#Override
public K ceilingKey(K key) {
return ceilingEntry(key).getKey();
}
#Override
public K higherKey(K key) {
return higherEntry(key).getKey();
}
#Override
public Map.Entry<K, V> pollFirstEntry() {
final Map.Entry<K, V> e = firstEntry();
if (e != null) {
remove(e.getKey());
}
return e;
}
#Override
public Map.Entry<K, V> pollLastEntry() {
final Map.Entry<K, V> e = lastEntry();
if (e != null) {
remove(e.getKey());
}
return e;
}
#Override
public NavigableMap<K, V> descendingMap() {
return new ForwardingNavigableMap<K, V>() {
#Override
protected NavigableMap<K, V> delegate() {
return AbstractNavigableMap.this;
}
#Override
public NavigableMap<K, V> descendingMap() {
return new StandardDescendingMap();
}
}.descendingMap();
}
#Override
public NavigableSet<K> navigableKeySet() {
return new ForwardingNavigableMap<K, V>() {
#Override
protected NavigableMap<K, V> delegate() {
return AbstractNavigableMap.this;
}
#Override
public NavigableSet<K> navigableKeySet() {
return new StandardNavigableKeySet();
}
}.navigableKeySet();
}
#Override
public NavigableSet<K> descendingKeySet() {
return descendingMap().navigableKeySet();
}
#Override
public SortedMap<K, V> subMap(K fromKey, K toKey) {
return subMap(fromKey, true, toKey, false);
}
#Override
public NavigableMap<K, V> subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) {
return NavigableMapSubMap.subMapOf(this, fromKey, fromInclusive, toKey, toInclusive);
}
#Override
public SortedMap<K, V> headMap(K toKey) {
return headMap(toKey, false);
}
#Override
public NavigableMap<K, V> headMap(K toKey, boolean inclusive) {
return NavigableMapSubMap.headMapOf(this, toKey, inclusive);
}
#Override
public SortedMap<K, V> tailMap(K fromKey) {
return tailMap(fromKey, true);
}
#Override
public NavigableMap<K, V> tailMap(K fromKey, boolean inclusive) {
return NavigableMapSubMap.tailMapOf(this, fromKey, inclusive);
}
#Override
public Set<Map.Entry<K, V>> entrySet() {
return new NavigableMapEntrySet<K, V>(this);
}
}
/**
* Provides a {#link NavigableMap} view of a key range of an underlying
* {#link NavigableMap}.
*
* #param <K> the type of keys maintained by this map
* #param <V> the type of mapped values
*/
class NavigableMapSubMap<K, V> extends AbstractNavigableMap<K, V> {
private static final int UNSPECIFIED = 0;
private static final int INCLUSIVE = 1;
private static final int EXCLUSIVE = 2;
private final NavigableMap<K, V> base;
private final Comparator<? super K> comparator;
#NullableDecl
private final K fromKey;
private final int fromBound;
#NullableDecl
private final K toKey;
private final int toBound;
private NavigableMapSubMap(NavigableMap<K, V> base, K fromKey, int fromBound, K toKey,
int toBound) {
this.base = base;
comparator = effectiveComparator(base.comparator());
if (fromBound != UNSPECIFIED && toBound != UNSPECIFIED &&
comparator.compare(fromKey, toKey) > 0) {
throw new IllegalArgumentException("fromKey > toKey");
}
this.fromKey = fromKey;
this.fromBound = fromBound;
this.toKey = toKey;
this.toBound = toBound;
}
#SuppressWarnings({ "unchecked", "rawtypes" })
private static <K> Comparator<? super K> effectiveComparator(
#NullableDecl Comparator<? super K> comparator) {
return comparator != null ? comparator : (Comparator) Ordering.natural();
}
static <K, V> NavigableMap<K, V> subMapOf(NavigableMap<K, V> base, K fromKey,
boolean fromInclusive, K toKey, boolean toInclusive) {
return new NavigableMapSubMap<>(base, fromKey, fromInclusive ? INCLUSIVE : EXCLUSIVE,
toKey, toInclusive ? INCLUSIVE : EXCLUSIVE);
}
static <K, V> NavigableMap<K, V> headMapOf(NavigableMap<K, V> base, K toKey, boolean inclusive) {
return new NavigableMapSubMap<>(base, null, UNSPECIFIED, toKey, inclusive ? INCLUSIVE
: EXCLUSIVE);
}
static <K, V> NavigableMap<K, V> tailMapOf(NavigableMap<K, V> base, K fromKey, boolean inclusive) {
return new NavigableMapSubMap<>(base, fromKey, inclusive ? INCLUSIVE : EXCLUSIVE, null,
UNSPECIFIED);
}
#Override
public Comparator<? super K> comparator() {
return base.comparator();
}
#NullableDecl
private boolean aboveLowerBound(#NullableDecl K key, boolean inclusive) {
if (fromBound != UNSPECIFIED) {
final int cmp = comparator.compare(fromKey, key);
if (fromBound == INCLUSIVE || !inclusive ? cmp > 0 : cmp >= 0) {
return false;
}
}
return true;
}
#NullableDecl
private boolean belowUpperBound(#NullableDecl K key, boolean inclusive) {
if (toBound != UNSPECIFIED) {
final int cmp = comparator.compare(key, toKey);
if (toBound == INCLUSIVE || !inclusive ? cmp > 0 : cmp >= 0) {
return false;
}
}
return true;
}
#NullableDecl
private boolean inBounds(#NullableDecl K key, boolean inclusive) {
return aboveLowerBound(key, inclusive) && belowUpperBound(key, inclusive);
}
#NullableDecl
private Map.Entry<K, V> lowerBound(#NullableDecl Map.Entry<K, V> e) {
if (e != null && fromBound != UNSPECIFIED) {
final int cmp = comparator.compare(fromKey, e.getKey());
if (fromBound == INCLUSIVE ? cmp > 0 : cmp >= 0) {
return null;
}
}
return e;
}
#NullableDecl
private Map.Entry<K, V> upperBound(#NullableDecl Map.Entry<K, V> e) {
if (e != null && toBound != UNSPECIFIED) {
final int cmp = comparator.compare(e.getKey(), toKey);
if (toBound == INCLUSIVE ? cmp > 0 : cmp >= 0) {
return null;
}
}
return e;
}
#NullableDecl
private Map.Entry<K, V> bound(#NullableDecl Map.Entry<K, V> e) {
return lowerBound(upperBound(e));
}
#Override
#SuppressWarnings("unchecked")
public boolean containsKey(Object key) {
return inBounds((K) key, true) && base.containsKey(key);
}
#Override
#SuppressWarnings("unchecked")
public V get(Object key) {
return inBounds((K) key, true) ? base.get(key) : null;
}
#Override
#SuppressWarnings("unchecked")
public V remove(Object key) {
return inBounds((K) key, true) ? base.remove(key) : null;
}
#Override
public Map.Entry<K, V> lowerEntry(K key) {
return bound(base.lowerEntry(key));
}
#Override
public Map.Entry<K, V> floorEntry(K key) {
return bound(base.floorEntry(key));
}
#Override
public Map.Entry<K, V> ceilingEntry(K key) {
return bound(base.floorEntry(key));
}
#Override
public Map.Entry<K, V> higherEntry(K key) {
return bound(base.higherEntry(key));
}
#Override
public Map.Entry<K, V> firstEntry() {
switch (fromBound) {
case UNSPECIFIED:
return base.firstEntry();
case INCLUSIVE:
return upperBound(base.floorEntry(fromKey));
default:
return upperBound(base.higherEntry(fromKey));
}
}
#Override
public Map.Entry<K, V> lastEntry() {
switch (toBound) {
case UNSPECIFIED:
return base.firstEntry();
case INCLUSIVE:
return lowerBound(base.ceilingEntry(toKey));
default:
return lowerBound(base.lowerEntry(toKey));
}
}
private NavigableMap<K, V> subMap(K fromKey, int fromBound, K toKey, int toBound) {
if (fromBound == UNSPECIFIED) {
fromKey = this.fromKey;
fromBound = this.fromBound;
} else if (!inBounds(fromKey, fromBound == INCLUSIVE)) {
throw new IllegalArgumentException("fromKey out of range");
}
if (toBound == UNSPECIFIED) {
toKey = this.toKey;
toBound = this.toBound;
} else if (!inBounds(toKey, toBound == INCLUSIVE)) {
throw new IllegalArgumentException("toKey out of range");
}
return new NavigableMapSubMap<>(base, fromKey, fromBound, toKey, toBound);
}
#Override
public NavigableMap<K, V> subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) {
return subMap(fromKey, fromInclusive ? INCLUSIVE : EXCLUSIVE, toKey,
toInclusive ? INCLUSIVE : EXCLUSIVE);
}
#Override
public NavigableMap<K, V> headMap(K toKey, boolean inclusive) {
return subMap(null, UNSPECIFIED, toKey, inclusive ? INCLUSIVE : EXCLUSIVE);
}
#Override
public NavigableMap<K, V> tailMap(K fromKey, boolean inclusive) {
return subMap(fromKey, inclusive ? INCLUSIVE : EXCLUSIVE, null, UNSPECIFIED);
}
#Override
public Set<Map.Entry<K, V>> entrySet() {
return new NavigableMapEntrySet<K, V>(this) {
#Override
protected Map.Entry<K, V> higherEntry(Map.Entry<K, V> from) {
// omit unnecessary lower bound check of outer higherEntry
return upperBound(base.higherEntry(from.getKey()));
}
};
}
}
/**
* Provides a {#link Map#entrySet()} implementation based on a
* {#link NavigableMap} using the following methods:
*
* <ul>
* <li>{#link NavigableMap#firstEntry()}</li>
* <li>{#link NavigableMap#higherEntry(Object)}</li>
* <li>{#link Map#get(Object)}</li>
* <li>{#link Map#remove(Object)}</li>
* </ul>
*
* Note that {#link Collection#size()} is implemented in terms of iteration
* using {#code firstEntry} and {#code higherEntry}, and therefore executes in
* time proportional to the map size. Subclasses may wish to provide a more
* efficient implementation.
*
* #param <K> the type of keys maintained by this map
* #param <V> the type of mapped values
*/
public class NavigableMapEntrySet<K, V> extends AbstractSet<Map.Entry<K, V>> {
private final NavigableMap<K, V> map;
/**
* Constructs a {#link NavigableMapEntrySet} for the given map.
*
* #param map the map whose entries are being represented
*/
public NavigableMapEntrySet(NavigableMap<K, V> map) {
this.map = map;
}
/**
* Used by {#link #iterator()} to return an entry associated with the least
* key in this map, or {#code null} if the map is empty. The default
* implementation simply calls {#link NavigableMap#firstEntry()}; subclasses
* may wish to provide a more efficient implementation.
*
* #return an entry with the least key, or null if the map is empty
*/
protected Map.Entry<K, V> firstEntry() {
return map.firstEntry();
}
/**
* Used by {#link #iterator()} to return an entry associated with the least
* key strictly greater than the key of the given entry, or {#code null} if
* there is no such key. The default implementation simply calls
* {#link NavigableMap#higherEntry(Object)} with the key of the given entry;
* subclasses may wish to provide a more efficient implementation.
*
* #param from the reference entry, which must have been returned from this
* entry set
* #return an entry with the least key greater than the given entry, or null
* if there is no such key
*/
protected Map.Entry<K, V> higherEntry(Map.Entry<K, V> from) {
return map.higherEntry(from.getKey());
}
#Override
public Iterator<Map.Entry<K, V>> iterator() {
return new Iterator<Map.Entry<K, V>>() {
#NullableDecl
Map.Entry<K, V> prevEntry = null;
#NullableDecl
Map.Entry<K, V> nextEntry = firstEntry();
#Override
public boolean hasNext() {
return nextEntry != null;
}
#Override
public Map.Entry<K, V> next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
prevEntry = nextEntry;
nextEntry = higherEntry(nextEntry);
return prevEntry;
}
#Override
public void remove() {
if (prevEntry == null) {
throw new IllegalStateException();
}
map.remove(prevEntry.getKey());
prevEntry = null;
}
};
}
#Override
public int size() {
return Iterators.size(iterator());
}
#Override
public boolean contains(Object o) {
if (o instanceof Map.Entry) {
final Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
return Objects.equals(map.get(e.getKey()), e.getValue());
}
return false;
}
#Override
public boolean remove(Object o) {
if (o instanceof Map.Entry) {
final Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
final Object key = e.getKey();
if (Objects.equals(map.get(key), e.getValue())) {
map.remove(key);
return true;
}
}
return false;
}
}
ForwardingNavigableMap
You must have a NavigableMap in advance if you use ForwardingNavigableMap which uses decorator pattern. So this is not what you need.
AbstractMap
AbstractMap provides skeletal implementation of the Map. This class helps you implement a Map which is interface of NavigableMap, but without 'Navigable logic'. Anyway, NavigableMap is-a Map, so AbstractMap helps to setup a NavigableMap. Compared to ForwardingNavigableMap, I think AbstractMap can be your better choice.
I'd suggest to consider something simpler as NavigableMap is a pretty rich interface and you may end up spending a lot of time on features you may not need. Even a plain Map has tons of methods and AbstractMap hardly helps as it provides only very rudimentary implementations (either inefficient or throwing UnsupportedOperationException).
If you could use List (with sublist for "navigation"), you'd save yourself a lot of work. Anyway, look at guava-testlib for a good testsuite.
If you really need a NavigableMap, I'm afraid, you're out of luck. I guess, there's no suitable base class as it's impossible to write something useful often enough.
For instance, it seems like firstEntry of a sub/tail map could just call ceilingEntry(minKey) or higherEntry(minKey), depending on whether minKey is inclusive.
Yes, but this is a rather simple case. And there may be a faster way for firstEntry for a given implementation.
How can I construct a SortedMap on top of Guava's computing map (or vice versa)? I want the sorted map keys as well as computing values on-the-fly.
The simplest is probably to use a ConcurrentSkipListMap and the memoizer idiom (see JCiP), rather than relying on the pre-built unsorted types from MapMaker. An example that you could use as a basis is a decorator implementation.
May be you can do something like this.It's not a complete implementation.Just a sample to convey the idea.
public class SortedComputingMap<K, V> extends TreeMap<K, V> {
private Function<K, V> function;
private int maxSize;
public SortedComputingMap(int maxSize, Function<K, V> function) {
this.function = function;
this.maxSize = maxSize;
}
#Override
public V put(K key, V value) {
throw new UnsupportedOperationException();
}
#Override
public void putAll(Map<? extends K, ? extends V> map) {
throw new UnsupportedOperationException();
}
#Override
public V get(Object key) {
V tmp = null;
K Key = (K) key;
if ((tmp = super.get(key)) == null) {
super.put(Key, function.apply(Key));
}
if (size() > maxSize)
pollFirstEntry();
return tmp;
}
public static void main(String[] args) {
Map<Integer, Long> sortedMap = new SortedComputingMap<Integer, Long>(3,
new Function<Integer, Long>() {
#Override
public Long apply(Integer n) {
Long fact = 1l;
while (n != 0)
fact *= n--;
return fact;
}
});
sortedMap.get(12);
sortedMap.get(1);
sortedMap.get(2);
sortedMap.get(5);
System.out.println(sortedMap.entrySet());
}
}
If you need the thread safety, this could be tricky, but if you don't I'd recommend something close to Emil's suggestion, but using a ForwardingSortedMap rather than extending TreeMap directly.