Nested HashMaps and Declaration - java

I'm trying to experiment with Maps and I have this doubt:
Map<String, Object> input = new LinkedHashMap<String, Object>();
String operator = "in";
String argument = "foo";
String field = "AvailabilityStatus";
Map<String, Object> innerMap = new LinkedHashMap<String, Object>();
innerMap.put(operator, argument);
input.put(field, innerMap);
The function call for the above code is
String output = FunctionA(input);
Seems to work fine but changing the input to:
Map<String, Map<String, Object>> input = new LinkedHashMap<String, LinkedHashMap<String, Object>>();
doesn't let me call the function the same way. The functionA is:
public static String FunctionA(Map<String, Object> filters) throws Throwable {
//logic goes here
}
Aren't the two statements essentially trying to do the same thing?

Alternately, you could make the FunctionA method like this:
public static String FunctionA(Map<String, ? extends Object> filters) throws Throwable{
//logic goes here
}
Doing this will be happy then!
FunctionA(new HashMap<String, LinkedHashMap<String, Object>>());

A Map<String, Map<String, Object>> is not a subtype of Map<String, Object>, even though Map<String, Object>is a subtype of Object.
Indeed, uou can store whatever object you want in the latter, whereas you can only store Map<String, Object> instances in the former. That's why the compiler doesn't let you pass a Map<String, Map<String, Object>> to a method taking a Map<String, Object> as argument.
If it let you do that, the method could store Strings, Integers or Bananas into the map, which would thus ruin the type-safety of the map, supposed to only contain instances of Map<String, Object>.

Related

Is it possible to use mulitple possible types in Java?

What I want is a property that is a map with a key of type String, where the value can be either a String OR another map:
Map<String, String or Map<String, String>> traits();
Is this possible in Java?
There are two ways to do it, either your generics or Object.
Map<String, ?> stringObjectMap.get = new HashMap<>();
Map<String, Object> stringObjectMap.get = new HashMap<>();
if(stringObjectMap.get("key") instanceof Map) return (Map)stringObjectMap.get("key");
else return (String)stringObjectMap.get("key");

Generics Handling LinkedHashMap<String, LinkedHashMap>

I have never been that good in Generics but I used SnakeYaml.
Is there a way to let me fix this code
public class MyService{
private static Map<String, LinkedHashMap> myYamlMap;
public static void filter(Map<String, String>){
//myYaml map reads the YAML File using SnakeYaml
//Snake Yaml returns data in this format <String,LinkedHashMap>
Yaml yaml = new Yaml();
Object object = yaml.load(reader);
Map<String, LinkedHashMap> myYamlMap = (Map<String, LinkedHashMap>)object;
LinkedHashMap<String, LinkedHashMap> mainMap = (LinkedHashMap<String, LinkedHashMap>)myYamlMap.get("sample");
}
}
and get away with this compile time warnings?
Multiple markers at this line
- Line breakpoint:MyService [line: 69] - filter(Map<String, String>)
- Type safety: Unchecked cast from LinkedHashMap to LinkedHashMap<String,LinkedHashMap>
- LinkedHashMap is a raw type. References to generic type LinkedHashMap<K,V> should be
parameterized
- LinkedHashMap is a raw type. References to generic type LinkedHashMap<K,V> should be
parameterized
Snakeyaml..uses LinkedHashMap in its construct and I wanted to get away with the casting.
Given your code:
private static Map<String, LinkedHashMap> myYamlMap;
LinkedHashMap<String, LinkedHashMap> mainMap = (LinkedHashMap<String, LinkedHashMap>)myYamlMap.get("sample");
This doesn't make sense.
private static Map<String, LinkedHashMap> myYamlMap;
This should probably be
private static Map<String, Map<Key, Value>;
myYamlMap = new LinkedHashMap<String, Map<Key, Value>>;
myYamlMap.put("key1", new LinkedHashMap<Key,Value>();
for some Key and Value types, which aren't specified in your code...
OR something more complex -- see below
LinkedHashMap<String, LinkedHashMap> mainMap = (LinkedHashMap<String, LinkedHashMap>)myYamlMap.get("sample");
Your use of get here seems to imply that myYamlMap should be
private static Map<String, Map<String, Map<Key, Value>> myYamlMap;
myYamlMap = new LinkedHashMap<String, Map<String, Map<Key,Value>>>;
Map<Key,Value> temp = new LinkedHashMap<Key,Value>();
temp.put(k1, value1);
myYamlMap.put("sample", temp);
since you seem to be expecting get() to return a Map<String,Map<Key,Value>> from within the outer collection.
NOW you can do
Map<String, Map<Key,Value>> mainMap = myYamlMap.get("sample");
The reason for using the Map interface is that nowhere in your code do you use methods specific to LinkedHashMap so declarations should all be using just Map<...> except when instantiating the maps.

How I use the put() method for Maps in Maps

this is my Map in Maps I created
private static Map<Integer, Map<String, String>> directory = new HashMap<>();
and I want to know how I use the put() method for this above.
I tried this for example:
directory.put(5,"Test1","Test2");
but this is not correct. I get the following message by eclipse:
The method put(Integer, Map<String,String>) in the type Map<Integer,Map<String,String>> is not applicable for the arguments (int, String, String)
But I have to hold on to the guidelines for the university. There is a JUnitTest and there is also the put method. Just look how they did this:
addEntry(1, "Name", "Dall", "FirstName", "Karl", "phoneNr", "4711");
and thats my addEntry Method from the university
public static void addEntry(int nrP, String... attrValPairs) throws IllegalArgumentException
Yours is a nested Map, so you need to have a Map object in the value of the outer Map:
if(!directory.containsKey(5)) {
directory.put(5, new HashMap<>());
}
directory.get(5).put("Test1", "Test2");
I would implement it like this
private static final Map<Integer, Map<String, String>> directory = new HashMap<>();
public static void put(Integer key1, String key2, String value) {
Map<String, String> map = directory.get(key1);
if (map == null)
directory.put(key1, map = new HashMap<>());
map.put(key2, value);
}
private static Map<Integer, Map<String, String>> directory = new HashMap<>();
This accepts 2 arguments Integer and Map
So you can only put Integer and Map but here directory.put(5,"Test1","Test2"); you are putting 3 arguements Integer,String and String
Hence this error The method put(Integer, Map<String,String>) in the type Map<Integer,Map<String,String>> is not applicable for the arguments (int, String, String)
To solve your problem I would suggest you to create another map like this
Map<String, String> directory1 = new HashMap<String,String>();
Now put the Strings in this map first like this
directory1.put("Test1","Test2");
and now you can use this
directory.put(5,directory1);

How to change HashMap<K, V> value type from Object to String?

What is the easiest/best way to convert
Map<String, Object>
to
HashMap<String, String>
The API I am using has methods that return a Map but it would be easier if I didn't have to cast the Object to a String each time.
Also, is this even worth doing? Would a HashMap be faster/more efficient than a Map?
I'm assuming I'll have to loop through the original Map and copy the values to the new HashMap.
Thanks in advance!
You can use the constructor as others mentioned:
Map<String, String> newMap = new HashMap(oldMap);
This will only work however if you know that the Objects in question are really Strings.
but there is something I should mention:
Do not confuse interfaces with classes. Map is just an interface; a contract which contains only definitions. A class on the other hand is a concrete implementation of an interface. So it does not make any difference in terms of perfomrance if you use the Map interface or its runtime type (HashMap). It can make a difference however if you swap the implementations (to TreeMap for example).
Edit:
Here is the verbose solution which is liked by EE guys (no casting/rawtypes warning involved):
public class MapConverter {
public Map<String, String> convert(Map<String, Object> oldMap) {
Map<String, String> ret = new HashMap<String, String>();
for (String key : oldMap.keySet()) {
ret.put(key, oldMap.get(key).toString());
}
return ret;
}
}
Using the copy constructor on raw types works:
HashMap<String, String> hashMap = new HashMap(map);
However, the solution is ugly as the type system is ignored.
EDIT1:
When you execute
public static void main(String[] args) throws IllegalArgumentException,
InterruptedException, IOException {
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("Bla", new Object());
HashMap<String, String> hashMap = new HashMap(map);
System.out.println(hashMap.get("Bla").getClass());
}
you get the class cast exception:
Exception in thread "main" java.lang.ClassCastException: java.lang.Object cannot be cast to java.lang.String
It is thrown when "System.out.println(hashMap.get("Bla").getClass());" is executed.
Consequently, the casts are actually delayed.
EDIT2:
You can avoid the copy with
HashMap<String, String> hashMap = (HashMap)map;
However, the problem remains the same as the following code shows:
public static void main(String[] args) throws IllegalArgumentException,
InterruptedException, IOException {
HashMap<String, Object> oldMap = new HashMap<String, Object>();
oldMap.put("Bla", new Object());
HashMap<String, String> hashMap = (HashMap)oldMap;
System.out.println(hashMap.get("Bla").getClass());
}
It behaves like the other example above in EDIT1.
EDIT3:
What about using a lambda?
Map<String, Object> map = new HashMap<String, Object>();
// 1
final Stream<Map.Entry<String, Object>> entries = map.entrySet()
.stream();
final Function<Map.Entry<String, Object>, String> keyMapper = (
Map.Entry<String, Object> entry) -> entry.getKey();
final Function<Map.Entry<String, Object>, String> valueMapper = (
Map.Entry<String, Object> entry) -> {
final Object value = entry.getValue();
if (value instanceof String) {
return (String) value;
} else {
throw new ClassCastException("Value '" + value + "' of key '"
+ entry.getKey() + "' cannot be cast from type "
+ ((value != null) ? value.getClass().getName() : null)
+ " to type " + String.class.getName());
}
};
final BinaryOperator<String> duplicateHandler = (key1, key2) -> {
throw new IllegalStateException(String.format("Duplicate key %s",
key1));
};
final HashMap<String, String> hashMap = entries.collect(Collectors
.toMap(keyMapper, valueMapper, duplicateHandler, HashMap::new));
System.out.println(hashMap);
If map only has string-to-string entries, it will copy them all.
E.g. Insert
map.put("aKey", "aValue");
at comment 1. It will print
{aKey=aValue}
which is fine.
If you have at least one string-to-non-string entry in your map, copying will fail.
E.g. Insert
map.put("aKey", 42);
at comment 1. It will print
Exception in thread "main" java.lang.ClassCastException: Value '42' of key ' aKey' cannot be cast from type java.lang.Integer to type java.lang.String
at ...
which shows the string-to-non-string entry.
I know this solution is not so simple but it is safe.
If you know the types of key and value (like <String, String>), you can just cast the whole map:
Map<String, String> newMap = (HashMap<String, String>)oldMap;
If you need a separate Map instance, you can use the constructor of HashMap like this:
HashMap<String, String> = new HashMap<String, String>((HashMap<String, String>) oldMap);

Map<String, Map<String, Boolean>> myMap = new HashMap<String,HashMap<String,Boolean>>();

Why doesn't that work in java, but this does
Map<String, Map<String, Boolean>> myMap = new HashMap<String,Map<String,Boolean>>();
Just to clarify the below alteration of the nested HashMap shows a compiler error, whereas the above does not not; with a Map (not hashmap)
Map<String, Map<String, Boolean>> myMap = new HashMap<String,HashMap<String,Boolean>>();
This is because generics in Java are invariant, i.e. even if class B is an A, a Collection<B> is not a Collection<A>.
And this is for a good reason. If your example were legal, this would be possible:
Map<String, HashMap<String, Boolean>> myHashMap = new HashMap<String,HashMap<String,Boolean>>();
Map<String, Map<String, Boolean>> myMap = myHashMap;
myMap.put("oops", new TreeMap<String, Boolean>());
HashMap<String, Boolean> aHashMap = myMap.get("oops"); // oops - ClassCastException!
In the second case myMap is a map which keys are of type String and values are of type Map<String, Boolean>. HashMap<String, Boolean> is not a Map<String, Boolean> it implements it. Therefore, this will compile:
Map<String, ? extends Map<String, Boolean>> myOtherMap =
new HashMap<String,HashMap<String,Boolean>>();
I think that's because of the difference between Map<String, Boolean> and HashMap<String,Boolean>.
Indeed, the generics are here a specification, which must be the same on both sides. (or at least that's my opinion).

Categories