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

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.

Related

Make class (which wraps a Map) usable in for-loop in Java

I have a class which is basically wrapper around a Map (that also holds some business logic obviously).
What I would like to be able to do is this:
for(Object o: instanceOfMyClass) { ... }
So I want to loop over the values of the Map inside my class. Which interfaces do I need to implement in my class (Iterator, Iterable, ...)?
I guess in the interface to implement I somehow need to return an Interator; how can I "reuse" the iterator for the Map (through Map.entrySet()) keeping in mind that I only want to have the values exposed in the iterator of my class?
Thanks a lot!
It's as simple as implementing Iterable. In your case, you want to implement Iterable<SomeType>:
public class Main implements Iterable<String>
{
private final Map<String, String> myMap = new HashMap<>();
{
myMap.put("hello", "world");
myMap.put("aaa", "bbb");
}
#Override
public Iterator<String> iterator()
{
return Collections.unmodifiableMap(myMap).values().iterator();
}
}
Here's a test method, with the output below:
public static void main(String... args)
{
for (String entry : new Main())
{
System.out.println("Value: " + entry);
}
}
Value: bbb Value: world
Implementing Iterable is what allows you to use the for(Foo f : foo) syntax, but if you're wrapping a Map you might want to implement the Map.forEach() method instead. It's a little bit nicer when you can directly address the key and value separately (although the question is now about accessing values only).
// Mostly copied from Map.forEach()
// Adjust generic parameters if necessary
public void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
for (Map.Entry<K, V> entry : internalMap.entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
action.accept(k, v);
}
}
If the default implementation is enough, you can of course just delegate to internalMap.forEach(action);.

add elements in arraylist inside hashmap

I am trying to build a dynamic hashmap of type String and arraylist dynamically. I have some json data coming from server and instead of declaring many arraylist I want to save them in hashmap with String as key and arraylist as value.
Here is what I am doing now
ArrayList<classproperty> allStu;
ArrayList<classproperty> allEmp;
HashMap<String, ArrayList<classproperty>> hash;
if (type.equals("Student")) {
prop = new classproperty("Student", info.getJSONObject(i).getJSONObject("student").getJSONArray("class").getJSONObject(s).getJSONObject("type").getString("name"));
allStu.add(prop);
}
if (type.equals("Emp")) {
prop = new esSignalProperty("Emp", info.getJSONObject(m).getJSONObject("emp").getJSONObject(s).getJSONObject("dept").getString("name"));
allemp.add(prop);
}
hash.put("Student", allStu);
hash.put("Emp", allemp);
So it is ugly way to do it...and I would like to do it by directly putting into hashmap without declaring so many arraylist. please ignore json string extraction as it is just dummy.
You just need to initialize the arraylist in the beginning and then just add value based on key. if you know the keys that I guess you know you can do like this
public HashMap<String, ArrayList<classproperty>> hash
hash.put("Student", new ArrayList<classproperty>());
hash.put("Emp", new ArrayList<classproperty>());
after just as #steffen mention but with minor change
hash.get("Student").add(prop);
hash.get("Emp").add(prop);
It is nothing very different from what other purposed but may be still can help.
hash.get("Student").put(prop)
could be a solution, as you know the key inside the map.
Using this way you can leave out the 'allStu' and 'allEmp' Lists, as you can get them directly from the map.
I would suggest using MultiMap from Guava library that already supports this. If you don't plan to import this library, then you can roll your own manually as a wrapper of a Map<K, List<V>>:
//basic skeleton of the multimap
//as a wrapper of a map
//you can define more methods as you want/need
public class MyMultiMap<K,V> {
Map<K, List<V>> map;
public MyMultiMap() {
map = new HashMap<K, List<V>>();
}
//in case client needs to use another kind of Map for implementation
//e.g. ConcurrentHashMap
public MyMultiMap(Map<K, List<V>> map) {
this.map = map;
}
public void put(K key, V value) {
List<V> values = map.get(key);
if (values == null) {
//ensure that there will always be a List
//for any key/value to be inserted
values = new ArrayList<V>();
map.put(key, values);
}
values.add(value);
}
public List<V> get(K key) {
return map.get(key);
}
#Override
public String toString() {
//naive toString implementation
return map.toString();
}
}
Then just use your multimap:
MyMultiMap myMultiMap = new MyMultiMap<String, ClassProperty>();
myMultiMap.put("student", new ClassProperty(...));
myMultiMap.put("student", new ClassProperty(...));
System.out.println(myMultiMap);

Java Map with multiple keys

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>>

Runtime type of java.util.Map key and value

Problem:
I am trying to find the java type of a map's key and value, but without having to iterate over the map and then using instanceof, and want to know it even if the map is empty.
The context:
CKEditorConfig which can be downloaded from here (http://ckeditor.com/download) is implemented using a map. But you cannot set this map using injection because setter method is not provided, and there's no constructor provided to set it using constructor injection. However, there are five addConfigValue method (see below), which I can use to add those values.
public void addConfigValue(final String key, final Number value);
public void addConfigValue(final String key, final String value);
public void addConfigValue(final String key, final Boolean value);
public void addConfigValue(final String key, final Map<String, ? extends Object> value);
public void addConfigValue(final String key, final List<? extends Object> value);
I will be using ckeditor in a couple of places in my j2ee/java web app, and so I figured I should create a factory class that returns the settings needed to initialize the editor by some name. But I wanted to externalize these config and set them in the spring context file. so since I cannot create the config object I have to use regular maps, and then build the config object using the map. but I need to validate those maps coming from spring before building the object.
This is my method for building the config object:
#SuppressWarnings("unchecked")
public CKEditorConfig buildConfigFromMap(Map<String, Object> configMap) {
CKEditorConfig config = new CKEditorConfig();
for (String key : configMap.keySet()) {
Object val = configMap.get(key);
if (val instanceof Number)
config.addConfigValue(key, (Number) val);
else if (val instanceof String)
config.addConfigValue(key, (String) val);
else if (val instanceof Boolean)
config.addConfigValue(key, (Boolean) val);
else if (val instanceof List) {
config.addConfigValue(key, (List<Object>) val);
} else if (val instanceof Map) {
// TODO
}
throw new IllegalArgumentException("invalid map value types");
}
return config;
}
And as you can see the if the object is a map, I need to be able to validate if its key is a String. I know I can do something like this:
} else if (val instanceof Map) {
for (Object mapKey : ((Map<Object, Object>) val).keySet()) {
if (!(mapKey instanceof String))
throw new IllegalArgumentException("invalid map value types");
}
config.addConfigValue(key, (Map<String, Object>) val);
}
but the whole exercise makes me think I am not doing the right thing? any suggestions? I am open to using a totally different approach. I appreciate your input.
It's impossible, Java generics are erased at runtime.
In fact they only exist in the compiler (and the source code).
You cannot inspect the actual types of keys and values and deduce the generics used for the Map from that.
You can only infer generics in source code for Maps under your control.
It seems to me you want to force Spring to only supply Map<String, ?>.
Here is a workaround :
public class StringKeyMapEntry { public String key; public Object value; }
inject List<StringKeyMapEntry> or StringKeyMapEntry[] (not sure if Spring can do arrays) and convert it to Map<String, ?>
You have a number of settings that are referenced by a string ... I would use one method and change the signature to this.
public void configure(final Map<String, ? extends Object> map);
And then do what you want with each setting, you could call each setting directly or loop through it.

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