Java Map with multiple keys - java

I need to create a map, with 3 columns: 2 keys, and 1 value. So each value will contain 2 keys of different classtypes, and can be fetched by using either one. But my problem is that HashMap/Map supports only 1 key, and 1 value. Is there a way to create something like Map<Key1, Key2, Value> instead of Map<Key, Value>? so Value can be fetched by either using its Key1 or Key2.
I apologize if it is a duplicate or a bad question, but I couldn't find a similar one on Stack Overflow.
P.S: I don't want to create 2 maps: Map<Key1, Value> and Map<Key2, Value> nor creating nested maps I am looking for a multikey table, just one like above.

You're probably going to have to write a custom implementation of a map-like class to implement this. I agree with #William Price above, the easiest implementation will be to simply encapsulate two Map instances. Be careful using the Map interface, as they rely on equals() and hashCode() for key identity, which you intend to break in your contract.

Write class with your requirements by yourself:
import java.util.HashMap;
import java.util.Map;
public class MMap<Key, OtherKey, Value> {
private final Map<Key, Value> map = new HashMap<>();
private final Map<OtherKey, Value> otherMap = new HashMap<>();
public void put(Key key, OtherKey otherKey, Value value) {
if (key != null) { // you can change this, if you want accept null.
map.put(key, value);
}
if (otherKey != null) {
otherMap.put(otherKey, value);
}
}
public Value get(Key key, OtherKey otherKey) {
if (map.containsKey(key) && otherMap.containsKey(otherKey)) {
if (map.get(key).equals(otherMap.get(otherKey))) {
return map.get(key);
} else {
throw new AssertionError("Collision. Implement your logic.");
}
} else if (map.containsKey(key)) {
return map.get(key);
} else if (otherMap.containsKey(otherKey)) {
return otherMap.get(otherKey);
} else {
return null; // or some optional.
}
}
public Value getByKey(Key key) {
return get(key, null);
}
public Value getByOtherKey(OtherKey otherKey) {
return get(null, otherKey);
}
}

Just store the value twice:
Map<Object, Value> map = new HashMap<>();
map.put(key1, someValue);
map.put(key2, someValue);
The thing is, it doesn't really matter what type the key is, so use a generic bound that allows both key types - Object is fine.
Note that the parameter type of Map#get() method is just Object anyway, so from a look-up perspective there's no value in having separate maps (the type of the key is only relevant for put()).

Have you looked into Apache Commons Collection multi map interface?
https://commons.apache.org/proper/commons-collections/javadocs/api-3.2.1/org/apache/commons/collections/MultiMap.html

Take a look at guava's Table collection that can be used in your context
http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/collect/Table.html
Table<String,String,String> ==> Map<String,Map<String,String>>

Related

Java Map - log message when key is not found in getOrDefault

I have a Map<String, List<SomeClass>> someMap and I'm retrieving the value based on someKey and for each element of the list of SomeClass I'm performing other operations.
someMap.getOrDefault(someKey, new ArrayList<>()).forEach(...)
I also want to be able to log messages when I don't find someKey. How would I be able to achieve it optimally? Is there any other function/way to achieve this behavior?
Map<String, List<String>> map = new HashMap<>();
List<String> l = new ArrayList<>();
l.add("b");
map.put("a", l);
Yes, you can do it in a single statement. Use .compute().
map.compute("a", (k, v) -> {
if (v == null) {
System.out.println("Key Not Found");
return new ArrayList<>();
}
return v;
}).forEach(System.out::println);
There's also computeIfAbsent() which will only compute the lambda if the key is not present.
Note, from the documentation:
Attempts to compute a mapping for the specified key and its current
mapped value (or null if there is no current mapping).
This will add the key which was not found in your map.
If you want to remove those keys later, then simply add those keys to a list inside the if and remove them in one statement like this:
map.keySet().removeAll(listToRemove);
You can create a function to do that. For example, I created a function which will get the value from the map, return it if it is not null, or an empty list otherwise. Before returning the empty list, you can run a Runnable action. The main benefit of that is that you can do more than just logging there.
#Slf4j
public class Main {
public static Collection<String> retrieveOrRun(Map<String, Collection<String>> map, String key, Runnable runnable) {
final Collection<String> strings = map.get(key);
if (strings == null) {
runnable.run();
return Collections.emptyList();
} else {
return strings;
}
}
public static void main(String[] args) {
Map<String, Collection<String>> map = new HashMap<>();
Collection<String> strings = retrieveOrRun(map, "hello", () -> log.warn("Could not find a value for the key : {}", "hello"));
}
}
I think you have two choices:
Either you use a wrapper method, doing the actual call (getOrDefault, etc) and handling missing keys.
public static <K,V> V getOrDefault(Map<K,V> map, K key, V defaultValue) {
V value = map.get(key);
if (value == null) {
logMissingValue(key);
return defaultValue;
}
return value;
}
Or you create new implementation of Map doing just that, with a delegation to method that should be delegated (I won't do here in this example, but Eclipse work pretty well: Alt + Shift + S > Create delegate methods):
class LoggerMap<K,V> implements Map<K,V> {
private final Map<K,V> internal;
public LoggerMap(Map<K,V> internal) {
this.internal = Objects.requireNonNull(internal, "internal");
}
#Override
public V getOrDefault(K key, V defaultValue) {
... if not found logMissingValue(key); ...
}
}
Now about which is optimal, that depends on your needs: if you know you will always use the wrapper method, then your missing keys will always be logged. Creating a new map implementation would be overkill.
If your need is to log absolutely all missing keys - even if foreign code (for example, some API taking a map as a parameter), then your best choice is a map implementation:
In terms of performance, I don't think you should worry about delegation: I did not test it using a benchmark, but the JVM should be able to optimize that.
There are other parts where a key might return a missing value (eg: remove, get, ...), using such an implementation will allow you to easily trace those as well.

Hashmap that keeps original key/value when duplicate key entered

Is it possible for a Hashmap to keep its original key/value pair when a duplicate key is entered?
For example, let's say I have something like this:
Map<String, String> map = new HashMap<String, String>();
map.put("username","password1");
map.put("username","password2");
I want the original key/value pair - username, password1 to be kept and not be overrode by username, password2.
Is this possible? If not, how can I eliminate duplicate entries from being put into the map?
As mentioned, you can use putIfAbsent if you use Java 8.
If you are on an older Java version you can use a ConcurrentHashMap instead, which has a putIfAbsent method.
Of course, you get the additional overhead of thread safety, but if you are not writing an extremely performance sensitive application it should not be a concern.
If not on Java 8, you have some options.
The most straightforward is the verbose code everywhere
Object existingValue = map.get(key);
if(existingValue == null){
map.put(key,newValue);
}
You could have a utility method to do this for you
public <T,V> void addToMapIfAbsent(Map<T,V> map, T key, V value){
V oldValue = map.get(key);
if(oldValue == null){
map.put(key,value);
}
}
Or extend a flavor of Map and add it there.
public class MyMap<T,V> extends HashMap<T,V>{
public void putIfNotExist(T key, V value){
V oldValue = get(key);
if(oldValue == null){
put(key,value);
}
}
}
Which allows you to create a Map thusly
Map<String,String> map = new MyMap<>();
EDIT: Although, to get to the MyMap method, of course, you'll need to have the map variable declared as that type. So anywhere you need that, you'll have to take an instance of MyMap instead of Map.
https://docs.oracle.com/javase/8/docs/api/java/util/HashMap.html#putIfAbsent-K-V-
If you are using Java 8, you can use putIfAbsent.

Java HashMap in methods

Is it possible to have a method requiring a HashMap and be able to provide any HashMap with Strings as keys? Some kind of generic data Type to put instead of 'Value'?
public void example(HashMap<String, Value> hashMap) {
//Stuff
}
example(new HashMap<String, Integer>);
HashMap<String, String> exampleMap = new HashMap<>();
example(exampleMap);
Alternatively, is it possible to check the key/value type of the map, other than looping through all the keys/value and check instanceof (without stopping it with return)?
public Boolean example(HashMap<String, Value> hashMap) {
for (Value value : hashMap.values())) {
if (value instanceof String) {
return true; //<- Unwanted
}
}
}
EDIT:
Let me explain my problem a bit further. I have a method:
public static Object getEIC(HashMap<String, Object> map, String key) {
for (String keys : map.keySet()) {
if (keys.equalsIgnoreCase(key)) {
return map.get(keys);
}
}
return null;
}
EIC stands for equalsIgnoreCase. So I need some generic return Type as well. Thanks for the answers so far, and thanks on forehand for the answers on this!
You could do:
HashMap<String, Object>
But that's terrible though because then you end up doing instanceof all the time. What you really need to do is understand what the key problem is and then think of inheritance. Can you define an interface that all your value objects would implement? For instance GeometricShape which could be implemented by Rectangle and Circle.
Also, you can define a HashMap as follows:
HashMap<String, ? extends SomeClass>
And as others pointed out, it's best to use the interface Map rather than a specific implementation e.g. HashMap.
Lastly, as I pointed out in a comment, it seems you are just trying to implement a Map<key, value> where you want to have case-insensitive keys. If so have a look at www.stackoverflow.com/questions/8236945/
The answer is to use a CaseInsensitiveMap. Thanks, everyone, for helping me and especially David Brossard for suggesting this.
You can implement the static method you are looking for using generics:
public static <V> V getEIC(Map<String, V> map, String key) {
for (String k : map.keySet()) {
if (k.equalsIgnoreCase(key)) {
return map.get(key);
}
}
return null;
}
however, you may wish to consider an enhanced Map Is there a good way to have a Map<String, ?> get and put ignoring case?
Note, however, that there is a subtle difference between this solution and using case-less equals - an ordinary Map<String,V> could hold a value for "Key" and "key" while a case-less Map would not. This method would arbitrarily choose one of them while a case-less Map would choose the most recent addition.
Yes, but requiring Map<String, Value> is better. If you don't care what value is ask for Map<String, ?>. If you do but don't yet know what it will be, use Map<String, ? extends T>
Requiring HashMap is programing to an implementation rather than an interface. There is rarely a good reason to do that.
And no. If you don't trust whoever built the map and want to be sure the map contains only the types it's supposed to contain you have to loop and check each one. However, you can abstract that away in a method that will check for you so you don't have to look at it.
public class CheckedCast
{
public static <K,V> Map<K,V> mapOf(Class<? extends K> classK,
Class<? extends V> classV,
Map<?,?> m)
{
for (Map.Entry<?, ?> e: m.entrySet())
{
classK.cast( e.getKey() );
classV.cast( e.getValue() );
}
#SuppressWarnings("unchecked")
Map<K,V> result = (Map<K,V>) m;
return result;
}
}

Retrieve an ArrayListMultimap Key

I'm using Guava's ArrayListMultimap<K,V> collection to map Integers to Strings. The class provides a method called containsValue(Object value) which checks if the Multimap contains the specified value for any key. Once I determine that is true, what's the best way to retrieve said key?
ArrayListMultimap<String, Integer> myMap = ArrayListMultimap.create();
if (myMap.containsValue(new Integer(1))
{
// retrieve the key?
}
Instead of using containsValue you could iterate over myMap.entries() which returns a collection of all key-value pairs. The iterator generated by the returned collection traverses the values for one key, followed by the values of a second key, and so on:
Integer toFind = new Integer(1);
for (Map.Entry<String, Integer> entry: myMap.entries()) {
if (toFind.equals(entry.getValue())) {
// entry.getKey() is the first match
}
}
// handle not found case
If you look at the implementation of containsValue it just iterates over the map's values so the performance of doing this with map.entries() instead of map.values() should be about the same.
public boolean containsValue(#Nullable Object value) {
for (Collection<V> collection : map.values()) {
if (collection.contains(value)) {
return true;
}
}
return false;
}
In the general case of course there isn't necessarily a unique key for a given value so unless you know that in your map each value only occurs against a single key you would need to specify the behaviour e.g. if you wanted the first key or last key.

Java Class that implements Map and keeps insertion order?

I'm looking for a class in java that has key-value association, but without using hashes. Here is what I'm currently doing:
Add values to a Hashtable.
Get an iterator for the Hashtable.entrySet().
Iterate through all values and:
Get a Map.Entry for the iterator.
Create an object of type Module (a custom class) based on the value.
Add the class to a JPanel.
Show the panel.
The problem with this is that I do not have control over the order that I get the values back, so I cannot display the values in the a given order (without hard-coding the order).
I would use an ArrayList or Vector for this, but later in the code I need to grab the Module object for a given Key, which I can't do with an ArrayList or Vector.
Does anyone know of a free/open-source Java class that will do this, or a way to get values out of a Hashtable based on when they were added?
Thanks!
I suggest a LinkedHashMap or a TreeMap. A LinkedHashMap keeps the keys in the order they were inserted, while a TreeMap is kept sorted via a Comparator or the natural Comparable ordering of the keys.
Since it doesn't have to keep the elements sorted, LinkedHashMap should be faster for most cases; TreeMap has O(log n) performance for containsKey, get, put, and remove, according to the Javadocs, while LinkedHashMap is O(1) for each.
If your API that only expects a predictable sort order, as opposed to a specific sort order, consider using the interfaces these two classes implement, NavigableMap or SortedMap. This will allow you not to leak specific implementations into your API and switch to either of those specific classes or a completely different implementation at will afterwards.
LinkedHashMap will return the elements in the order they were inserted into the map when you iterate over the keySet(), entrySet() or values() of the map.
Map<String, String> map = new LinkedHashMap<String, String>();
map.put("id", "1");
map.put("name", "rohan");
map.put("age", "26");
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println(entry.getKey() + " = " + entry.getValue());
}
This will print the elements in the order they were put into the map:
id = 1
name = rohan
age = 26
If an immutable map fits your needs then there is a library by google called guava (see also guava questions)
Guava provides an ImmutableMap with reliable user-specified iteration order. This ImmutableMap has O(1) performance for containsKey, get. Obviously put and remove are not supported.
ImmutableMap objects are constructed by using either the elegant static convenience methods of() and copyOf() or a Builder object.
You can use LinkedHashMap to main insertion order in Map
The important points about Java LinkedHashMap class are:
It contains only unique elements.
A LinkedHashMap contains values based on the key.
It may have one null key and multiple null values.
It is same as HashMap instead maintains insertion order
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>
But if you want sort values in map using User-defined object or any primitive data type key then you should use TreeMap For more information, refer this link
You can maintain a Map (for fast lookup) and List (for order) but a LinkedHashMap may be the simplest. You can also try a SortedMap e.g. TreeMap, which an have any order you specify.
Either You can use LinkedHashMap<K, V> or you can implement you own CustomMap which maintains insertion order.
You can use the Following CustomHashMap with the following features:
Insertion order is maintained, by using LinkedHashMap internally.
Keys with null or empty strings are not allowed.
Once key with value is created, we are not overriding its value.
HashMap vs LinkedHashMap vs CustomHashMap
interface CustomMap<K, V> extends Map<K, V> {
public boolean insertionRule(K key, V value);
}
#SuppressWarnings({ "rawtypes", "unchecked" })
public class CustomHashMap<K, V> implements CustomMap<K, V> {
private Map<K, V> entryMap;
// SET: Adds the specified element to this set if it is not already present.
private Set<K> entrySet;
public CustomHashMap() {
super();
entryMap = new LinkedHashMap<K, V>();
entrySet = new HashSet();
}
#Override
public boolean insertionRule(K key, V value) {
// KEY as null and EMPTY String is not allowed.
if (key == null || (key instanceof String && ((String) key).trim().equals("") ) ) {
return false;
}
// If key already available then, we are not overriding its value.
if (entrySet.contains(key)) { // Then override its value, but we are not allowing
return false;
} else { // Add the entry
entrySet.add(key);
entryMap.put(key, value);
return true;
}
}
public V put(K key, V value) {
V oldValue = entryMap.get(key);
insertionRule(key, value);
return oldValue;
}
public void putAll(Map<? extends K, ? extends V> t) {
for (Iterator i = t.keySet().iterator(); i.hasNext();) {
K key = (K) i.next();
insertionRule(key, t.get(key));
}
}
public void clear() {
entryMap.clear();
entrySet.clear();
}
public boolean containsKey(Object key) {
return entryMap.containsKey(key);
}
public boolean containsValue(Object value) {
return entryMap.containsValue(value);
}
public Set entrySet() {
return entryMap.entrySet();
}
public boolean equals(Object o) {
return entryMap.equals(o);
}
public V get(Object key) {
return entryMap.get(key);
}
public int hashCode() {
return entryMap.hashCode();
}
public boolean isEmpty() {
return entryMap.isEmpty();
}
public Set keySet() {
return entrySet;
}
public V remove(Object key) {
entrySet.remove(key);
return entryMap.remove(key);
}
public int size() {
return entryMap.size();
}
public Collection values() {
return entryMap.values();
}
}
Usage of CustomHashMap:
public static void main(String[] args) {
System.out.println("== LinkedHashMap ==");
Map<Object, String> map2 = new LinkedHashMap<Object, String>();
addData(map2);
System.out.println("== CustomHashMap ==");
Map<Object, String> map = new CustomHashMap<Object, String>();
addData(map);
}
public static void addData(Map<Object, String> map) {
map.put(null, "1");
map.put("name", "Yash");
map.put("1", "1 - Str");
map.put("1", "2 - Str"); // Overriding value
map.put("", "1"); // Empty String
map.put(" ", "1"); // Empty String
map.put(1, "Int");
map.put(null, "2"); // Null
for (Map.Entry<Object, String> entry : map.entrySet()) {
System.out.println(entry.getKey() + " = " + entry.getValue());
}
}
O/P:
== LinkedHashMap == | == CustomHashMap ==
null = 2 | name = Yash
name = Yash | 1 = 1 - Str
1 = 2 - Str | 1 = Int
= 1 |
= 1 |
1 = Int |
If you know the KEY's are fixed then you can use EnumMap. Get the values form Properties/XML files
EX:
enum ORACLE {
IP, URL, USER_NAME, PASSWORD, DB_Name;
}
EnumMap<ORACLE, String> props = new EnumMap<ORACLE, String>(ORACLE.class);
props.put(ORACLE.IP, "127.0.0.1");
props.put(ORACLE.URL, "...");
props.put(ORACLE.USER_NAME, "Scott");
props.put(ORACLE.PASSWORD, "Tiget");
props.put(ORACLE.DB_Name, "MyDB");
I don't know if it is opensource, but after a little googling, I found this implementation of Map using ArrayList. It seems to be pre-1.5 Java, so you might want to genericize it, which should be easy. Note that this implementation has O(N) access, but this shouldn't be a problem if you don't add hundreds of widgets to your JPanel, which you shouldn't anyway.
Whenever i need to maintain the natural order of things that are known ahead of time, i use a EnumMap
the keys will be enums and you can insert in any order you want but when you iterate it will iterate in the enum order (the natural order).
Also when using EnumMap there should be no collisions which can be more efficient.
I really find that using enumMap makes for clean readable code.
Here is an example

Categories

Resources