Proper use of generics with collection instance factory - java

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<>());

Related

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.

Getting error in variable assigning when using generics

I have made two Lists like
List<LearnerEnrollment> learnerEnrollmentList = new ArrayList<LearnerEnrollment>();
List<LearnerCourseEnrollError> enrollErrorList = new ArrayList<LearnerCourseEnrollError>();
Then i made two Maps like
Map<String, List<LearnerCourseEnrollError>> courseErrorMap = new HashMap<String, List<LearnerCourseEnrollError>>();
Map<String, List<LearnerEnrollment>> courseSuccessMap = new HashMap<String, List<LearnerEnrollment>>();
Then i made another Map to hold the above two Maps like
Map<String, Map<String, List<Object>>> courseMap = new HashMap<String, Map<String, List<Object>>>();
Then i use the following code to add items in lists;
for (com.softech.vu360.lms.model.Course course : courseList) {
Object result = getEnrollmentForCourse(customer, learner, course);
if (result instanceof LearnerEnrollment) {
LearnerEnrollment newEnrollment = (LearnerEnrollment)result;
learnerEnrollmentList.add(newEnrollment);
} else if (result instanceof String) {
String errorMessage = (String)result;
LearnerCourseEnrollError enrollError = new LearnerCourseEnrollError(errorMessage, course);
enrollErrorList.add(enrollError);
}
}
Now i am putting values in the Map
courseSuccessMap.put(learner.getVu360User().getUsername(), learnerEnrollmentList);
courseErrorMap.put(learner.getVu360User().getUsername(), enrollErrorList);
courseMap.put("successfulCoursesMap", courseSuccessMap);
courseMap.put("unSuccessfulCoursesMap", courseErrorMap);
return courseMap;
But i am getting error at these two lines
courseMap.put("successfulCoursesMap", courseSuccessMap);
courseMap.put("unSuccessfulCoursesMap", courseErrorMap);
that
The method put(String, Map<String,List<Object>>) in the type
Map<String,Map<String,List<Object>>> is not applicable for the arguments
(String, Map<String,List<LearnerEnrollment>>)
The method put(String, Map<String,List<Object>>) in the type
Map<String,Map<String,List<Object>>> is not applicable for the arguments
(String, Map<String,List<LearnerCourseEnrollError>>)
Why?
My list type in the Map is List<Object> and List<LearnerEnrollment> is List <Object> because LearnerEnrollment extends Object. Why I am getting these errors ?
If i declare my Map like this
Map<String, Map<String, ?>> courseMap = new HashMap<String, Map<String, ?>>();
Then there is no error. Why i am getting error in first case?
Thanks
You said:
List<LearnerEnrollment> is List<Object>
This is wrong. If it were true, you would be able to do:
List<<LearnerEnrollment> list = new ArrayList<>();
List<Object> objectList = list;
objectList.add("Now what?");
And your type-safe list of LearnerEnrollment would suddenly contain a String.
This is because a List in java is not covariant. A List<LearnerEnrollment> is not a subclass of List<Object>.
See Java covariance for more information.

Initializing a Map with List inside

I need to use a Map with a List inside :
Map<String, List<String>> keyToGroup = new HashMap<String, ArrayList<String>>();
I am getting compiler error on this line in eclipse.
The only working thing seem to be changing the inside List in the Map to ArrayList
Map<String, ArrayList<String>> keyToGroup = new HashMap<String, ArrayList<String>>();
I had to change the signature of many interfaces' methods, but I still don't get it; why isn't the first definition work?
Isn't it the same, should not
Map<String, List<String>> keyToGroup
&
Map<String, ArrayList<String>>
be the same?
No, they're not. Consider this:
Map<String, List<String>> keyToGroup = new HashMap<String, ArrayList<String>>();
keyToGroup.put("foo", new LinkedList<String>());
The second line is fine, because a LinkedList<String> is a List<String> - but it's not logically fine in terms of adding it to a HashMap<String, ArrayList<String>>, because a LinkedList<String> is not an ArrayList<String>.
To make it clearer:
Map<String, ArrayList<String>> map1 = new HashMap<String, ArrayList<String>>();
Map<String, List<String>> map2 = map1; // This is invalid
map2.put("foo", new LinkedList<String>());
ArrayList<String> oops = map1.get("foo"); // Because this would be broken
This isn't just the case with collections as the type argument. It's even simpler to see with normal inheritance:
List<Banana> bunchOfBananas = new ArrayList<Banana>();
List<Fruit> fruitBowl = bunchOfBananas; // Invalid!
fruitBowl.add(new Apple());
Banana banana = bunchOfBananas.get(0);
Even though every banana is a fruit, so a "collection of bananas" is a "collection of fruit* in the sense of fetching them, not every fruit is a banana.
You can use wildcard parameterized types to help in some cases, but it depends on exactly what you're trying to achieve.
Ask yourself a question if you need particular list implementation in your Map or any List?
In case of particular implementation you can use your last example:
Map<String, ArrayList<String>> keyToGroup = new HashMap<String, ArrayList<String>>();
In case of any list just use:
Map<String, List<String>> keyToGroup = new HashMap<String, List<String>>();
keyToGroup.put("arraylist", new ArrayList<String());
keyToGroup.put("linkedlist", new LinkedList<String());
BTW the second option usually is better from design point of view so if you don't know exactly for now - try using second option first.
No they are not. Generics are not covariant in Java.
If they are covariant you can logically put any type of List instead of ArrayList which defeats the purpose of having generics.
Consider reading Effective Java (2nd Edition) Chapter 5: Generics which has very good explanation of Generics.
Another good read is http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html

Newbie generic parameter qu estion... <T>

Okay so can i achive this somehow:
String myString = "someString";
Class myClass = myString.getClass();
HashMap<mClass, Integer> = new HashMap<myClass, Integer>();
So i would like to create a new hashmap, with class type of the key of my variables like Integer or String...
This is not possible. I'll walk you through the possibilities.
You could create a helper method, using generics. This will work because of all generics are compiled into simple Objects.
public static <T> Map<T, Integer> createMap(Class<T> cl)
{
return new HashMap<T, Integer>();
}
Now, you could use it like this:
Map<String, Integer> map = createMap(String.class);
However, this will require you to know what T is at compile time. So this won't work:
String str = "Test";
Class cl = str.getClass();
Map<String, Integer> map = createMap(cl); // Doesn't compile.
So, to conclude, this helper method isn't worth anything, because you could simply write:
Map<String, Integer> map = new HashMap<String, Integer>();
Due to type erasure this would not work.
A possible (but more verbose way) is to create a factory method that returns a Map based on the passed argument, eg:
MapFactory.create(String.class);
EDIT: In answer to #millimoose comment about this being not different from direct instantiation (which is true):
You could try to implement your own Map or decorate or extend the HashMap implementation so that it retains type information.

java: HashMap<String, int> not working

HashMap<String, int> doesn't seem to work but HashMap<String, Integer> does work.
Any ideas why?
You can't use primitive types as generic arguments in Java. Use instead:
Map<String, Integer> myMap = new HashMap<String, Integer>();
With auto-boxing/unboxing there is little difference in the code. Auto-boxing means you can write:
myMap.put("foo", 3);
instead of:
myMap.put("foo", new Integer(3));
Auto-boxing means the first version is implicitly converted to the second. Auto-unboxing means you can write:
int i = myMap.get("foo");
instead of:
int i = myMap.get("foo").intValue();
The implicit call to intValue() means if the key isn't found it will generate a NullPointerException, for example:
int i = myMap.get("bar"); // NullPointerException
The reason is type erasure. Unlike, say, in C# generic types aren't retained at runtime. They are just "syntactic sugar" for explicit casting to save you doing this:
Integer i = (Integer)myMap.get("foo");
To give you an example, this code is perfectly legal:
Map<String, Integer> myMap = new HashMap<String, Integer>();
Map<Integer, String> map2 = (Map<Integer, String>)myMap;
map2.put(3, "foo");
GNU Trove support this but not using generics. http://trove4j.sourceforge.net/javadocs/gnu/trove/TObjectIntHashMap.html
You cannot use primitive types in HashMap. int, or double don't work. You have to use its enclosing type. for an example
Map<String,Integer> m = new HashMap<String,Integer>();
Now both are objects, so this will work.
int is a primitive type, you can read what does mean a primitive type in java here, and a Map is an interface that has to objects as input:
public interface Map<K extends Object, V extends Object>
object means a class, and it means also that you can create an other class that exends from it, but you can not create a class that exends from int.
So you can not use int variable as an object. I have tow solutions for your problem:
Map<String, Integer> map = new HashMap<>();
or
Map<String, int[]> map = new HashMap<>();
int x = 1;
//put x in map
int[] x_ = new int[]{x};
map.put("x", x_);
//get the value of x
int y = map.get("x")[0];
You can use reference type in generic arguments, not primitive type.
So here you should use
Map<String, Integer> myMap = new HashMap<String, Integer>();
and store value as
myMap.put("abc", 5);

Categories