Generics Handling LinkedHashMap<String, LinkedHashMap> - java

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.

Related

Proper use of generics with collection instance factory

I'm trying to do the following thing using Apache Commons Collections v4:
Map<Integer, List<String>> namesPerNumber =
MapUtils.lazyMap(
new HashMap<Integer, List<String>>(),
FactoryUtils.instantiateFactory(ArrayList.class));
namesPerNumber.get(1).add("Mickey");
But I get the following compiler error at the lazyMap call:
The method lazyMap(Map<K,V>, Factory<? extends V>) in the type MapUtils is not applicable for the arguments (HashMap<Integer,List<String&t;>, Factory<ArrayList>)
Is there any proper way to use the factory for generating lists in a map? I tried also this:
Map<Integer, List<String>> namesPerNumber =
MapUtils.lazyMap(
new HashMap<Integer, List<String>>(),
FactoryUtils.<List<String>instantiateFactory(ArrayList.class));
But then I get this error at the instantiateFactory call:
The parameterized method <List<String>>instantiateFactory(Class<List<String>>) of type FactoryUtils is not applicable for the arguments (Class<ArrayList>)
The only working solution I found is the following, but I find it ugly:
Map<Integer, List<String>> namesPerNumber3 =
MapUtils.lazyMap(
new HashMap<Integer, List<String>>(),
new Factory<List<String>>() {
#Override
public List<String> create() {
return new ArrayList<String>();
}
});
Any help appreciated.
Signed,
lostingenerics
Due to type erasure, class literals support only reifiable types or raw types, so ArrayList.class represents the raw type ArrayList, not the intended ArrayList<String>.
One way to solve this, is by using one unchecked operation:
#SuppressWarnings("unchecked") Class<ArrayList<String>> type = (Class)ArrayList.class;
Map<Integer, List<String>> namesPerNumber =
MapUtils.lazyMap(
new HashMap<Integer, List<String>>(),
FactoryUtils.instantiateFactory(type));
Note that the effect of #SuppressWarnings("unchecked") is intentionally limited to the single unchecked operation here.
Or you use
Map<Integer, List<String>> namesPerNumber =
MapUtils.lazyMap(
new HashMap<Integer, List<String>>(),
FactoryUtils.prototypeFactory(new ArrayList<String>()));
instead.
If you are using Java 8, the best option is
Map<Integer, List<String>> namesPerNumber =
MapUtils.lazyMap(new HashMap<>(), () -> new ArrayList<>());

Why <String, String> entry allowed for HashMap<Integer, String>()?

I have HashMap which generic type <Integer, String> i.e. key should be an Integer and value should be String for this HashMap.
I wrote bellow code which put String and getting no compilation and runtime error. Why?
Map map = new HashMap<Integer, String>();
map.put("a", "one");
System.out.println(map);
OUTPUT:
{a=one}
I have HashMap which generic type <Integer, String> ...
No you do not!
Map map = new HashMap<Integer, String>();
Means you have just a Map (because of Map map =). If you want Map<Integer, String> you must use:
Map<Integer, String> map = new HashMap<Integer, String>();
or, in later versions of Java
Map<Integer, String> map = new HashMap<>();
Added
The reason for this is that the right-hand-side of the assignment is a separate process and is evaluated first. In your case it creates a HashMap<Integer, String>.
Next the assignment happens, the compiler checks that HashMap<Integer, String> can be cast to Map (which is equivalent to Map<Object,Object> BTW) and the assignment is performed. From then on all references to map treat it as type Map<Object,Object> and can therefore hold any type for key or value.
You are adding content to Map map declared without specifying any generics types.
If you declare the map this way the compilator doesn't know how to check the map content.
If you change your map declaration to
Map<Integer, String> map = new HashMap<>();
Then you will have a compilation error.
Map map = new HashMap<Integer, String>();
Here your definition is type specified, however declaration is not. So, you are able to add any type to map.
The proper way for generic map declaration is
Map<Integer, String> map = new HashMap<Integer, String>();
or in new versions of Java, you can skip type in defintion.
Map<Integer, String> map = new HashMap<>();
Defining generics on the right side is more or less obsolete (grey font).
Following code wouldn't compile:
Map<Integer, String> map = new HashMap<>();
map.put("a", "one");
System.out.println(map);
with this explanation:
Wrong 1st argument type. Found: 'java.lang.String', required: 'java.lang.Integer'

Can I use type of a variable to declare another variable in Java?

Can I do something like this in Java?
HashMap<String, Child> childMap=new HashMap<String, Child>();
HashMap<String, childMap.typeName> parentMap=new HashMap<String, childMap.typeName>();
//instead of
HashMap<String, HashMap<String, Child>> parentMap=new HashMap<String, HashMap<String, Child>>();
or something like this
HashMap<String, HashMap<String, Child>> parent1=new HashMap<String, HashMap<String, Child>>();
parent1.typeName parent2=new parent1.typeName;
Because some time, if the map level is too deep or too complex, it is very hard to write and read.
Abbreviations are possible by defining a subclass:
class Str2Child extends HashMap<String, Child>>{}
class Str2Map extends HashMap<String,Str2Child>{}
Str2Map parent1 = new Str2Map();
No but you could shorten it if you're using Java 7 or higher. The compiler can infer the type parameters from the left side of the assignment and you can skip them altogether while creating the object HashMap<String, HashMap<String, Child>> parentMap = new HashMap<>();
In older versions of Java, you could resord to Guava's Maps class and its newHashMap method. HashMap<String, HashMap<String, Child>> parentMap = Maps.newHashMap();
Another thing you could possibly do is create a type that implements a certain specification of the generic HashMap.
public class HashMapStringChild extends HashMap<String, Child> {
}
and then use it as a type parameter
HashMap<String, HashMapStringChild> parent2 = new HashMap<>();
but personally, I find this a bit of a stretch. I certainly wouldn't overuse it and I'd be careful extending the collection classes.
Addendum
You should also note that you're effectively binding your API to a specific implementation of the Map interface (HashMap), or even worse, in case of introducing the new class (HashMapStringChild), to a specific, non-standard implementation.
What if at some point, you decide to keep your Child objects sorted at all times? You could do this by switching to a TreeMap but that would mean a big deal of refactoring.
You would be better off basing your API on a more general interface. This way you could switch from
Map<String, Map<String, Child>> map = new HashMap<String, HashMap<String, Child>>();
to
Map<String, Map<String, Child>> map = new HashMap<String, TreeMap<String, Child>>();
or
Map<String, Map<String, Child>> map = new TreeMap<String, TreeMap<String, Child>>();
or any other implementation without a hassle.
If you really want to make the map of String to Child a specific type, you could introduce an interface
public interface MapStringToChild extends Map<String, Child> {
}
Then you could keep your reference types general and use HashMap<String, Child>, TreeMap<String, Child>, HashMapStringChild or literally any other implementation mapping a String to a Child interchangeably, while keeping the code short.

Nested HashMaps and Declaration

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

Converting java.util.Properties to HashMap<String,String>

Properties properties = new Properties();
Map<String, String> map = new HashMap<String, String>(properties);// why wrong?
java.util.Properties is a implementation of java.util.Map, And java.util.HashMap's constructor receives a Map type param. So, why must it be converted explicitly?
This is because Properties extends Hashtable<Object, Object> (which, in turn, implements Map<Object, Object>). You attempt to feed that into a Map<String, String>. It is therefore incompatible.
You need to feed string properties one by one into your map...
For instance:
for (final String name: properties.stringPropertyNames())
map.put(name, properties.getProperty(name));
The efficient way to do that is just to cast to a generic Map as follows:
Properties props = new Properties();
Map<String, String> map = (Map)props;
This will convert a Map<Object, Object> to a raw Map, which is "ok" for the compiler (only warning). Once we have a raw Map it will cast to Map<String, String> which it also will be "ok" (another warning). You can ignore them with annotation #SuppressWarnings({ "unchecked", "rawtypes" })
This will work because in the JVM the object doesn't really have a generic type. Generic types are just a trick that verifies things at compile time.
If some key or value is not a String it will produce a ClassCastException error. With current Properties implementation this is very unlikely to happen, as long as you don't use the mutable call methods from the super Hashtable<Object,Object> of Properties.
So, if don't do nasty things with your Properties instance this is the way to go.
You could use Google Guava's:
com.google.common.collect.Maps.fromProperties(Properties)
How about this?
Map properties = new Properties();
Map<String, String> map = new HashMap<String, String>(properties);
Will cause a warning, but works without iterations.
The Java 8 way:
properties.entrySet().stream().collect(
Collectors.toMap(
e -> e.getKey().toString(),
e -> e.getValue().toString()
)
);
Properties implements Map<Object, Object> - not Map<String, String>.
You're trying to call this constructor:
public HashMap(Map<? extends K,? extends V> m)
... with K and V both as String.
But Map<Object, Object> isn't a Map<? extends String, ? extends String>... it can contain non-string keys and values.
This would work:
Map<Object, Object> map = new HashMap<Object, Object>();
... but it wouldn't be as useful to you.
Fundamentally, Properties should never have been made a subclass of HashTable... that's the problem. Since v1, it's always been able to store non-String keys and values, despite that being against the intention. If composition had been used instead, the API could have only worked with string keys/values, and all would have been well.
You may want something like this:
Map<String, String> map = new HashMap<String, String>();
for (String key : properties.stringPropertyNames()) {
map.put(key, properties.getProperty(key));
}
I would use following Guava API:
com.google.common.collect.Maps#fromProperties
Properties properties = new Properties();
Map<String, String> map = Maps.fromProperties(properties);
If you know that your Properties object only contains <String, String> entries, you can resort to a raw type:
Properties properties = new Properties();
Map<String, String> map = new HashMap<String, String>((Map) properties);
The problem is that Properties implements Map<Object, Object>, whereas the HashMap constructor expects a Map<? extends String, ? extends String>.
This answer explains this (quite counter-intuitive) decision. In short: before Java 5, Properties implemented Map (as there were no generics back then). This meant that you could put any Object in a Properties object. This is still in the documenation:
Because Properties inherits from Hashtable, the put and putAll methods
can be applied to a Properties object. Their use is strongly
discouraged as they allow the caller to insert entries whose keys or
values are not Strings. The setProperty method should be used instead.
To maintain compatibility with this, the designers had no other choice but to make it inherit Map<Object, Object> in Java 5. It's an unfortunate result of the strive for full backwards compatibility which makes new code unnecessarily convoluted.
If you only ever use string properties in your Properties object, you should be able to get away with an unchecked cast in your constructor:
Map<String, String> map = new HashMap<String, String>( (Map<String, String>) properties);
or without any copies:
Map<String, String> map = (Map<String, String>) properties;
this is only because the constructor of HashMap requires an arg of Map generic type and Properties implements Map.
This will work, though with a warning
Properties properties = new Properties();
Map<String, String> map = new HashMap(properties);
You can use this:
Map<String, String> map = new HashMap<>();
props.forEach((key, value) -> map.put(key.toString(), value.toString()));
First thing,
Properties class is based on Hashtable and not Hashmap. Properties class basically extends Hashtable
There is no such constructor in HashMap class which takes a properties object and return you a hashmap object. So what you are doing is NOT correct. You should be able to cast the object of properties to hashtable reference.
i use this:
for (Map.Entry<Object, Object> entry:properties.entrySet()) {
map.put((String) entry.getKey(), (String) entry.getValue());
}
When I see Spring framework source code,I find this way
Properties props = getPropertiesFromSomeWhere();
// change properties to map
Map<String,String> map = new HashMap(props)

Categories