Does Java have a HashMap with reverse lookup? - java

I have data that is organized in kind of a "key-key" format, rather than "key-value". It's like a HashMap, but I will need O(1) lookup in both directions. Is there a name for this type of data structure, and is anything like this included in Java's standard libraries? (or maybe Apache Commons?)
I could write my own class that basically uses two mirrored Maps, but I'd rather not reinvent the wheel (if this already exists but I'm just not searching for the right term).

There is no such class in the Java API. The Apache Commons class you want is going to be one of the implementations of BidiMap.
As a mathematician, I would call this kind of structure a bijection.

In addition to Apache Commons, Guava also has a BiMap.

Here is a simple class I used to get this done (I did not want to have yet another third party dependency). It does not offer all features available in Maps but it is a good start.
public class BidirectionalMap<KeyType, ValueType>{
private Map<KeyType, ValueType> keyToValueMap = new ConcurrentHashMap<KeyType, ValueType>();
private Map<ValueType, KeyType> valueToKeyMap = new ConcurrentHashMap<ValueType, KeyType>();
synchronized public void put(KeyType key, ValueType value){
keyToValueMap.put(key, value);
valueToKeyMap.put(value, key);
}
synchronized public ValueType removeByKey(KeyType key){
ValueType removedValue = keyToValueMap.remove(key);
valueToKeyMap.remove(removedValue);
return removedValue;
}
synchronized public KeyType removeByValue(ValueType value){
KeyType removedKey = valueToKeyMap.remove(value);
keyToValueMap.remove(removedKey);
return removedKey;
}
public boolean containsKey(KeyType key){
return keyToValueMap.containsKey(key);
}
public boolean containsValue(ValueType value){
return keyToValueMap.containsValue(value);
}
public KeyType getKey(ValueType value){
return valueToKeyMap.get(value);
}
public ValueType get(KeyType key){
return keyToValueMap.get(key);
}
}

If no collisions occur, you can always add both directions to the same HashMap :-)

Here my 2 cents.
Or you can use a simple method with generics. Piece of cake.
public static <K,V> Map<V, K> invertMap(Map<K, V> toInvert) {
Map<V, K> result = new HashMap<V, K>();
for(K k: toInvert.keySet()){
result.put(toInvert.get(k), k);
}
return result;
}
Of course you must have a map with unique values. Otherwise, one of them will be replaced.

Inspired by GETah's answer I decided to write something similar by myself with some improvements:
The class is implementing the Map<K,V>-Interface
The bidirectionality is really guaranteed by taking care of it when changing a value by a put (at least I hope to guarantee it hereby)
Usage is just like a normal map, to get a reverse view on the mapping call getReverseView(). The content is not copied, only a view is returned.
I'm not sure this is totally fool-proof (actually, it's probably not), so feel free to comment if you notice any flaws and I'll update the answer.
public class BidirectionalMap<Key, Value> implements Map<Key, Value> {
private final Map<Key, Value> map;
private final Map<Value, Key> revMap;
public BidirectionalMap() {
this(16, 0.75f);
}
public BidirectionalMap(int initialCapacity) {
this(initialCapacity, 0.75f);
}
public BidirectionalMap(int initialCapacity, float loadFactor) {
this.map = new HashMap<>(initialCapacity, loadFactor);
this.revMap = new HashMap<>(initialCapacity, loadFactor);
}
private BidirectionalMap(Map<Key, Value> map, Map<Value, Key> reverseMap) {
this.map = map;
this.revMap = reverseMap;
}
#Override
public void clear() {
map.clear();
revMap.clear();
}
#Override
public boolean containsKey(Object key) {
return map.containsKey(key);
}
#Override
public boolean containsValue(Object value) {
return revMap.containsKey(value);
}
#Override
public Set<java.util.Map.Entry<Key, Value>> entrySet() {
return Collections.unmodifiableSet(map.entrySet());
}
#Override
public boolean isEmpty() {
return map.isEmpty();
}
#Override
public Set<Key> keySet() {
return Collections.unmodifiableSet(map.keySet());
}
#Override
public void putAll(Map<? extends Key, ? extends Value> m) {
m.entrySet().forEach(e -> put(e.getKey(), e.getValue()));
}
#Override
public int size() {
return map.size();
}
#Override
public Collection<Value> values() {
return Collections.unmodifiableCollection(map.values());
}
#Override
public Value get(Object key) {
return map.get(key);
}
#Override
public Value put(Key key, Value value) {
Value v = remove(key);
getReverseView().remove(value);
map.put(key, value);
revMap.put(value, key);
return v;
}
public Map<Value, Key> getReverseView() {
return new BidirectionalMap<>(revMap, map);
}
#Override
public Value remove(Object key) {
if (containsKey(key)) {
Value v = map.remove(key);
revMap.remove(v);
return v;
} else {
return null;
}
}
}

Quite an old question here, but if someone else has brain block like I just did and stumbles on this, hopefully this will help.
I too was looking for a bi-directional HashMap, sometimes it is the simplest of answers that are the most useful.
If you do not wish to re-invent the wheel and prefer not to add other libraries or projects to your project, how about a simple implementation of parallel arrays (or ArrayLists if your design demands it).
SomeType[] keys1 = new SomeType[NUM_PAIRS];
OtherType[] keys2 = new OtherType[NUM_PAIRS];
As soon as you know the index of 1 of the two keys you can easily request the other. So your lookup methods could looks something like:
SomeType getKey1(OtherType ot);
SomeType getKey1ByIndex(int key2Idx);
OtherType getKey2(SomeType st);
OtherType getKey2ByIndex(int key2Idx);
This is assuming you are using proper object oriented structures, where only methods are modifying these arrays/ArrayLists, it would be very simple to keep them parallel. Even easier for an ArrayList since you would not have to rebuild if the size of the arrays change, so long as you add/remove in tandem.

Related

Is this wrapper of LinkedHashMap thread safe? If not how can it become thread safe?

I am attempting to implement a class with the following features by wrapping one of the built in Map classes.
Basic map functionality. (Only basic put, get, remove)
Can iterate over the values of the map in the order they were added. (as in LinkedHashMap)
Is thread safe.
Currently using a generic implementation but in the current use-case there will only ever be a handful of objects in the map. And additions/removal happen extremely infrequently - nominally additions occur only once.
Basically this one container should provide clients the ability to lookup a single Value object by Key AND/OR iterate through the Values (with order guarantee). In either case, the caller will likely be modifying the Value object, so it can't be read-only. Finally, callers may be coming from multiple threads.
This a minimized version of what I have right now:
public class MapWrapper<K, V> implements Iterable<V>
{
private Map<K, V> map = new LinkedHashMap<K, V>();
public void add(K key, V value)
{
// Does some other stuff
synchronized (map)
{
map.put(key, value);
}
}
public V get(K key)
{
V retVal;
synchronized (map)
{
retVal = map.get(key);
}
return retVal;
}
#Override
public Iterator<V> iterator()
{
List<V> values = new ArrayList<V>(map.values());
return values.iterator();
}
}
I feel like the iterator part is preventing this from being fully thread-safe. I see classes such as ConcurrentHashMap state that any client obtaining an iterator on the object MUST manually synchronize on the map object itself. Is there a way to make the code above thread-safe but still allow clients direct iterator access? Ie, I would like to be able to use a for-in loop, but I can not synchronize on the underlying map within MapWrapper.
MapWrapper<String, Object> test = new MapWrapper<String,Object>();
test.add("a", new Object());
test.add("c", new Object());
for (Object o: test) { o.setSomething(); }
I believe the following solves the issue by keeping ordered and hashed references, while maintaining thread safety with minimal effort:
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
public class OrderedConcurrentHashMap<K, V> implements Iterable<V>
{
private ConcurrentHashMap<K, V> map = new ConcurrentHashMap<>();
private ConcurrentLinkedQueue<V> queue = new ConcurrentLinkedQueue<>();
public void add(K key, V value)
{
map.put(key, value);
queue.add(value);
}
public V get(K key)
{
return map.get(key);
}
public boolean remove(K key)
{
return queue.remove(map.remove(key));
}
#Override
public Iterator<V> iterator()
{
return queue.iterator();
}
}
Given the following from the OP:
Only a handful of items
Rarely will items be added or removed
This is probably an optimal solution using only built-in collections and concurrency utilities.
The remove method here can be modified according to behavior that clients expect; this simplest implementation is just a suggestion.
Of particular note from the ConcurrentLinkedQueue docs for Java 8:
Iterators are weakly consistent, returning elements reflecting the state of the queue at some point at or since the creation of the iterator. They do not throw ConcurrentModificationException, and may proceed concurrently with other operations. Elements contained in the queue since the creation of the iterator will be returned exactly once.
And:
This class and its iterator implement all of the optional methods of the Queue and Iterator interfaces.
Assuming that you ensure V is thread-safe, this wrapper collection should ensure container thread safety.
Another thing to keep in mind is that the java.util.concurrent collections are not null-tolerant (ConcurrentHashMap.put(k, v), ConcurrentLinkedQueue.add(v), ConcurrentHashMap.get(k)).
From the put(k, v) doc:
Throws:
NullPointerException - if the specified key or value is null
From the add(v) doc:
Throws:
NullPointerException - if the specified element is null
From the get(k) doc:
Throws:
NullPointerException - if the specified key is null
I'm still thinking about how this would be handled.
It seems that introducing nulls significantly complicates things (as it always does).
EDIT: After some research, I found this: https://stackoverflow.com/a/9298113
I did come up with an extension of the implementation I shared above that handles nulls, but I'd be uncomfortable regarding race conditions outside of an experimental setting.
Planning on taking advantage of
java.util.concurrent.ConcurrentSkipListMap
The "natural ordering" provided by the keys being used is sufficient.
I think this should work.
public class MapWrapper<K, V> implements Iterable<V> {
private Map<K, V> map = new LinkedHashMap<K, V>();
private int currentSize = 0;
public void add(K key, V value) {
// Does some other stuff
synchronized (map) {
map.put(key, value);
currentSize++;
}
}
public V get(K key) {
V retVal;
synchronized (map) {
retVal = map.get(key);
currentSize--;
}
return retVal;
}
#Override
public Iterator<V> iterator() {
return new SyncIterator();
}
// Inner class example
private class SyncIterator implements Iterator<V> {
private int currentIndex = 0;
#Override
public boolean hasNext() {
return currentIndex < currentSize;
}
#Override
public V next() {
synchronized (map) {
List<V> values = new ArrayList<V>(map.values());
return values.get(currentIndex++);
}
}
#Override
public void remove() {
throw new UnsupportedOperationException();
}
}
}

Java key value pair without using collections

How would I manage key value pair as in HashMap without using Java Collections.
I found this (Pair class from Commons Lang)
Pair<String, String> keyValue = new ImmutablePair("key", "value");
Is this the only way to handle? Or are there other ways to handle?
personally i wouldn't, i like the built-in collections just fine.
having said that...
if you want something with better performance/smaller in-memory size, have a look at trove. they have map implementations that do away with the extra Entry (key-value pair) class. theyre especially good if your keys or values are primitives (say, ints)
if you want something with better predictability (another performance aspect), have a look at javolution. their collections classes offer much better worst-case times (think what happens when you insert something into a standard collection and you just triggered a resize...)
if youre looking for collections classes with more features, have a look at guava
Try Generics and create your own class if you don't like java collections:
public class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() {
return key;
}
public void setKey(K key) {
this.key = key;
}
public V getValue() {
return value;
}
public void setValue(V value) {
this.value = value;
}
public static void main(String[] args) {
Pair<String, String> pair = new Pair<String, String>("key", "value");
System.out.println(pair.getKey());
System.out.println(pair.getValue());
}
}
A Map is not a Collection, so use a HashMap.

Which data structure in Java should I use for storing key value pairs in ordered form? No duplicates [duplicate]

This question already has answers here:
java collection that has key/value pair, and is ordered according to insert order
(4 answers)
Closed 10 years ago.
I am working on a project and I need to store key value pairs (one-to-one mapping) in an ordered fashion. Then I should be able to retrieve the key using the value and value using the key. I have looked at Maps, Sets and Hash Tables, but they aren't ordered.
Also, though trivial, it would be great if we could DS retrieve the keys and values at once i.e., the interface supports such functions.
EDIT: The keys and values are all unique. Maintaining the inserted order is good enough.
Notice that you don't define what counts as "ordered". A LinkedHashMap enables iterating over the keys (and therefore values) in insertion-order. Conversely, a TreeMap lets you specify a sort order with a comparator, and ensures all items added to the map are stored in sorted order. 99 times out of 100, one of these classes should be all you need. Alternatively, Google's Guava project has several very nice BiMap implementations that you may find fits your needs.
I strongly caution you: if you think you need more than what these classes can provide, you are likely over-engineering your problem.
For reasons I can't fully justify, I implemented a proper UniqueOrderedBiMap for you, which is compatible with the Java Collections framework and all implemented functions run efficiently. You can use whatever underlying map you see fit (including an un-ordered map, if you really wanted) and keys and values are always unique. Notice that it is a very thin wrapper around a LinkedHashMap, because that's all you need, a LinkedHashMap with extra checks to ensure Values remain unique.
For the curious, check this answers revision history for a UniqueOrderedMap which lacks the getKey() and removeKey() methods, but more properly implements the Map interface, and only needs a HashSet, rather than a HashMap, to store the known values.
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
public class UniqueOrderedBiMap<K, V>implements Map<K, V> {
private Map<K, V> orderedMap;
private HashMap<V, K> valueMap;
public UniqueOrderedBiMap() {
this(new LinkedHashMap<K,V>());
}
public UniqueOrderedBiMap(Map<K, V> underlyingMap) {
orderedMap = underlyingMap;
valueMap = new HashMap<V, K>(orderedMap.size());
for(Map.Entry<K, V> e : orderedMap.entrySet()) {
if(!valueMap.containsKey(e.getValue())) { // Duplicate value
// could instead fail softly by removing the associated item from the map, but this seems cleaner/clearer.
// generally this constructor should be passed an empty map anyways
throw new IllegalArgumentException("Duplicate value "+e.getValue()+" found in underlying map.");
}
valueMap.put(e.getValue(), e.getKey());
}
}
#Override
public int size() {
return orderedMap.size();
}
#Override
public boolean isEmpty() {
return orderedMap.isEmpty();
}
#Override
public boolean containsKey(Object key) {
return orderedMap.containsKey(key);
}
#Override
public boolean containsValue(Object value) {
// more efficient than iterating over the map
return valueMap.containsKey(value);
}
#Override
public V get(Object key) {
return orderedMap.get(key);
}
public K getKey(V value) {
return valueMap.get(value);
}
// Likely want to implement a forcePut(K, V) method like Guava's BiMaps do
#Override
public V put(K key, V value) {
if(valueMap.containsKey(value)) {
throw new IllegalArgumentException("Cannot insert non-unique value "+value);
}
V ret = orderedMap.put(key, value);
valueMap.remove(ret);
valueMap.put(value, key);
return ret;
}
#Override
public V remove(Object key) {
V ret = orderedMap.remove(key);
valueMap.remove(ret);
return ret;
}
public K removeKey(V value) {
K ret = valueMap.remove(value);
orderedMap.remove(ret);
return ret;
}
#Override
public void putAll(Map<? extends K, ? extends V> m) {
// Existing Map implementation's putAll have some optimizations we
// could take advantage of, but this isn't unreasonable for a first pass
for(Entry<? extends K, ? extends V> e : m.entrySet()) {
put(e.getKey(), e.getValue());
}
}
#Override
public void clear() {
orderedMap.clear();
valueMap.clear();
}
#Override
public Set<K> keySet() {
return orderedMap.keySet();
}
#Override
public Collection<V> values() {
return orderedMap.values();
}
#Override
public Set<java.util.Map.Entry<K, V>> entrySet() {
return orderedMap.entrySet();
}
#Override
public boolean equals(Object o) {
if(o instanceof UniqueOrderedBiMap) {
UniqueOrderedBiMap<?,?> map = (UniqueOrderedBiMap<?,?>)o;
return orderedMap.equals(map.orderedMap);
}
return false;
}
#Override
public int hashCode() {
return orderedMap.hashCode();
}
#Override public String toString() {
return orderedMap.toString();
}
public static void main(String[] args) {
String[] names = { "Marcus", "Jim", "Tom", "Sam" };
String[] grades = { "A", "B", "D", "F" };
UniqueOrderedBiMap<String,String> insertionMap = new UniqueOrderedBiMap<>();
UniqueOrderedBiMap<String,String> sortedMap = new UniqueOrderedBiMap<>(new TreeMap<String,String>());
for(int i = 0; i < names.length; i++) {
insertionMap.put(names[i], grades[i]);
sortedMap.put(names[i], grades[i]);
}
// Poor man's assert
System.out.println(insertionMap.toString().equals("{Marcus=A, Jim=B, Tom=D, Sam=F}"));
System.out.println(sortedMap.toString().equals("{Jim=B, Marcus=A, Sam=F, Tom=D}"));
insertionMap.put("Tom", "C");
sortedMap.put("Tom", "C");
System.out.println(insertionMap.toString().equals("{Marcus=A, Jim=B, Tom=C, Sam=F}"));
System.out.println(sortedMap.toString().equals("{Jim=B, Marcus=A, Sam=F, Tom=C}"));
try {
insertionMap.put("Sam", "C");
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
try {
sortedMap.put("Sam", "C");
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
insertionMap.remove("Tom");
sortedMap.remove("Tom");
insertionMap.put("Sam", "C");
sortedMap.put("Sam", "C");
System.out.println(insertionMap.toString().equals("{Marcus=A, Jim=B, Sam=C}"));
System.out.println(sortedMap.toString().equals("{Jim=B, Marcus=A, Sam=C}"));
insertionMap.removeKey("A");
sortedMap.removeKey("A");
System.out.println(insertionMap.toString().equals("{Jim=B, Sam=C}"));
System.out.println(sortedMap.toString().equals("{Jim=B, Sam=C}"));
}
}
If you can use third party libraries then consider using an ImmutableBiMap. Its a Guava Collection class that provides
User specified iteration order
Normal mapping from keys to values and inverse mapping from values to keys
The one consideration is that once created the map is immutable and cannot be modified.
LinkedHashMap should be suitable for you. Read through the link
You will need two LinkedHashMap. You can create custom class that internally uses two LinkedHashMap. One for mapping keys to value and another one for mapping values to key.

Implements save (or commit) and rollback methods to a Map

I'm searching for a fast and convenient way to add save() and rollback() to a standard Map. Let's say i have an object 'table' of class Table which in turn has a private Map called 'rows'. What I'm trying to achieve is a fast and without memory waste method for Row to do something like:
row = new Row();
table.addRow(row).setValue("col1", "foo").setValue("col2", "bar").save();
row.setValue("col2", "beer");
System.out.println(table.getRows()); // 1. col1=foo, col2=bar
row.save();
System.out.println(table.getRows()); // 1. col1=foo, col2=beer
Actually, my design is quite trivial: when addRow() is called, i put() the row inside the map; no buffer, no temp elements; i simply pass the entire Row instance to rows collection. But I need a fast method and (if possible) avoiding the duplication of rows.
any idea?
This sounds too much like "I want to have the new and old values in memory, but I do not want to have the new and old values in memory".
Options:
a) A map of all the added elements, when save do putAll.
b) Your map, instead of <ClassKey, ClassValue>, holds <ClassKey, ClassValue2>. Value2 holds two items of ClassValue, the new and old instance. At save, you pass the new one (if any) to the old one. It will be useful only if you are changing most of the entries in each "transaction".
Not mentioned is the issue of deleting elements, which will bring you yet more joy. With option 2 you can set a boolean at Value2, with option a you will need more workarounds.
What I'm trying to achieve is a fast and without memory waste method
for Row to do something like:
You will waste memory, as you need to keep a "rollback" around somewhere in case it fails along the way. This is how databases handle these types of things.
Now to get the feature you want, you will need to implement your own custom transaction logic, this will allow you to correctly rollback / persist changes to your map. Now within this transaction you will need to keep track of everything that occurred during the transaction. This is because you will be doing temporary writes to your original map, while the transaction processes, subsequently you will need to have the ability to recover from a failed persist/update.
To avoid duplication of rows, the HashMap will already save you from that problem. Assuming you correctly implement a hash function that reports correctly when two objects generate the same code and are therefore "potentially", not definitely, equal in terms of hashing.
Keep two map fields inside custom Map class,
originalMap and temporaryMap.
Read operations are delegated to originalMap, and write operations to temporaryMap.
rollback() shallow-copies originalMap into temporaryMap and commit() vice versa. Since keys and values are never cloned, only the references are kept and memory is not "wasted"
package com.example;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import lombok.NonNull;
import lombok.ToString;
#ToString
public class TransactionalMap<K, V> implements Map<K, V> {
private Map<K, V> originalMap;
private Map<K, V> temporaryMap;
public TransactionalMap() {
this(new HashMap<>());
}
public TransactionalMap(#NonNull Map<K, V> impl) {
if (impl instanceof TransactionalMap) {
throw new IllegalArgumentException("Must provide valid implementation instance");
}
this.originalMap = new HashMap<>(impl);
this.temporaryMap = new HashMap<>(originalMap);
}
#Override
public int size() {
return originalMap.size();
}
#Override
public boolean isEmpty() {
return originalMap.isEmpty();
}
#Override
public boolean containsKey(Object key) {
return originalMap.containsKey(key);
}
#Override
public boolean containsValue(Object value) {
return originalMap.containsValue(value);
}
#Override
public V get(Object key) {
return originalMap.get(key);
}
#Override
public V put(K key, V value) {
return temporaryMap.put(key, value);
}
#Override
public V remove(Object key) {
return temporaryMap.remove(key);
}
#Override
public void putAll(Map<? extends K, ? extends V> m) {
temporaryMap.putAll(m);
}
#Override
public void clear() {
temporaryMap.clear();
}
#Override
public Set<K> keySet() {
return originalMap.keySet();
}
#Override
public Collection<V> values() {
return originalMap.values();
}
#Override
public Set<Entry<K, V>> entrySet() {
return originalMap.entrySet();
}
private void sync(Map<K, V> src, Map<K, V> tgt) {
tgt.putAll(src);
tgt.forEach((k, v) -> {
if (!src.containsKey(k)) tgt.remove(k);
});
}
public void commit() {
sync(temporaryMap, originalMap);
}
public void rollback() {
sync(originalMap, temporaryMap);
}
}
Uses lombok to generate boilerplate code

The proper way to look up an enum by value

I have several Java enums that looks something like below (edited for confidentiality, etc).
In each case, I have a lookup method that I'm really not satisfied with; in the example below, it is findByChannelCode.
public enum PresentationChannel {
ChannelA("A"),
ChannelB("B"),
ChannelC("C"),
ChannelD("D"),
ChannelE("E");
private String channelCode;
PresentationChannel(String channelCode) {
this.channelCode = channelCode;
}
public String getChannelCode() {
return this.channelCode;
}
public PresentationChannel findByChannelCode(String channelCode) {
if (channelCode != null) {
for (PresentationChannel presentationChannel : PresentationChannel.values()) {
if (channelCode.equals(presentationChannel.getChannelCode())) {
return presentationChannel;
}
}
}
return null;
}
}
The problem is, I feel silly doing these linear lookups when I could just be using a HashMap<String, PresentationChannel>. So I thought of the solution below, but it's a little messier that I would hope and, more to the point, I didn't care to re-invent the wheel when surely someone else has come across this. I wanted to get some of the sage wisdom of this group: what is the proper way to index an enum by value?
My solution:
ImmutableMap<String, PresentationChannel> enumMap = Maps.uniqueIndex(ImmutableList.copyOf(PresentationChannel.values()), new Function<PresentationChannel, String>() {
public String apply(PresentationChannel input) {
return input.getChannelCode();
}});
and, in the enum:
public static PresentationChannel findByChannelCode(String channelCode) {
return enumMap.get(channelCode);
}
I think you're using non-JDK classes here right?
A similar solution with JDK API:
private static final Map<String, PresentationChannel> channels = new HashMap<String, PresentationChannel>();
static{
for (PresentationChannel channel : values()){
channels.put(channel.getChannelCode(), channel);
}
}
I wanted to get some of the sage wisdom of this group: what is the proper way to index an enum by value?
Quite possibly not doing it at all.
While hash tables provide O(1) lookup, they also have quite a large constant overhead (for hash calculations etc), so for small collections a linear search may well be faster (if "the efficient way" is your definition of "the proper way").
If you just want a DRY way to do it, I suppose Guava's Iterables.find is an alternative:
return channelCode == null ? null : Iterables.find(Arrays.asList(values()),
new Predicate<PresentationChannel>() {
public boolean apply(PresentationChannel input) {
return input.getChannelCode().equals(channelCode);
}
}, null);
Why don't you name your members A, B, C, D, E and use valueOf?
I was looking for something similar and found on this site a simple, clean and straight to the point way. Create and initialize a static final map inside your enum and add a static method for the lookup, so it would be something like:
public enum PresentationChannel {
ChannelA("A"),
ChannelB("B"),
ChannelC("C"),
ChannelD("D"),
ChannelE("E");
private String channelCode;
PresentationChannel(String channelCode) {
this.channelCode = channelCode;
}
public String getChannelCode() {
return this.channelCode;
}
private static final Map<String, PresentationChannel> lookup
= new HashMap<String, PresentationChannel>();
static {
for(PresentationChannel pc : EnumSet.allOf(PresentationChannel.class)) {
lookup.put(pc.getChannelCode(), pc);
}
}
public static PresentationChannel get(String channelCode) {
return lookup.get(channelCode);
}
}
for few values that's ok, iteration through the values array(). One note only: use smth like that. values() clones the array on each invocation.
static final PresentationChannel[] values=values();
static PresentationChannel getByCode(String code){
if (code==null)
return null;
for(PresentationChannel channel: values) if (code.equals(channel.channelCode)) return channel;
return null;
}
if you have more Channels.
private static final Map<String code, PresentationChannel> map = new HashMap<String code, PresentationChannel>();
static{//hashmap sucks a bit, esp if you have some collisions so you might need to initialize the hashmap depending on the values count and w/ some arbitrary load factor
for(PresentationChannel channel: values()) map.put(channel.channelCode, channel);
}
static PresentationChannel getByCode(String code){
return map.get(code);
}
Edit:
So implement an helper interface, like shown below, another example why java syntax generics blows and sometimes - better not used.
Usage PresentationChannel channel = EnumRepository.get(PresentationChannel.class, "A");
There will be overhead but well, it's quite fool proof.
public interface Identifiable<T> {
T getId();
public static class EnumRepository{
private static final ConcurrentMap<Class<? extends Identifiable<?>>, Map<?, ? extends Identifiable<?>>> classMap = new ConcurrentHashMap<Class<? extends Identifiable<?>>, Map<?,? extends Identifiable<?>>>(16, 0.75f, 1);
#SuppressWarnings("unchecked")
public static <ID, E extends Identifiable<ID>> E get(Class<E> clazz, ID value){
Map<ID, E> map = (Map<ID, E>) classMap.get(clazz);
if (map==null){
map=buildMap(clazz);
classMap.putIfAbsent(clazz, map);
}
return map.get(value);
}
private static <ID, E extends Identifiable<ID>> Map<ID, E> buildMap( Class<E> clazz){
E[] enumConsts = clazz.getEnumConstants();
if (enumConsts==null)
throw new IllegalArgumentException(clazz+ " is not enum");
HashMap<ID, E> map = new HashMap<ID, E>(enumConsts.length*2);
for (E e : enumConsts){
map.put(e.getId(), e);
}
return map;
}
}
}
enum X implements Identifiable<String>{
...
public String getId(){...}
}
Minor warning: if you put Identifiable somewhere out there, and many projects/wepapp depend on it (and share it) and so on, it's possible to leak classes/classloaders.
Here is another way to implement an unmodifiable map:
protected static final Map<String, ChannelCode> EnumMap;
static {
Map<String, ChannelCode> tempMap = new HashMap<String, ChannelCode>();
tempMap.put("A", ChannelA);
tempMap.put("B", ChannelB);
tempMap.put("C", ChannelC);
tempMap.put("D", ChannelD);
tempMap.put("E", ChannelE);
EnumMap = Collections.unmodifiableMap(tempMap);
}
You can use EnumMap.get(someCodeAthroughE) to quickly retrieve the ChannelCode. If the expression is null then your someCodeAthroughE was not found.
If you are expecting the provided channelCode to always be valid then you can just try and get the correct instance of the enum using the valueOf() method. If the provided value is invalid you can return null or propagate the exception.
try {
return PresentationChannel.valueOf(channelCode);
catch (IllegalArgumentException e) {
//do something.
}

Categories