Why is Jackson's TypeReference abstract? It generally makes usages uglier since you need to include braces to create an inline class (and most linters force you into no curly braces on same line).
https://github.com/FasterXML/jackson-core/blob/master/src/main/java/com/fasterxml/jackson/core/type/TypeReference.java
new TypeReference<Map<String, String>>() {
};
Is this due to some obscure Java language limitation?
JB Nizet has answered why. I just wanted to demonstrate the principle of action.
You can try this for yourself on other classes:
List<String> a = new ArrayList<String>();
List<String> b = new ArrayList<String>() {};
System.out.println(a.getClass().getGenericSuperclass());
System.out.println(b.getClass().getGenericSuperclass());
Ideone demo
Output:
java.util.AbstractList<E>
java.util.ArrayList<java.lang.String>
As you can see, creating the anonymous subclass preserves the type information about what the concrete generic type of the list is at runtime.
A TypeReference does much the same:
new TypeReference<Map<String, String>>() { }
has a generic superclass:
TypeReference<java.util.Map<java.lang.String, java.lang.String>>
and from this, you can get the type Map<String, String>. If you created a type reference for another type:
new TypeReference<Map<Integer, Integer>>() { };
you could get the type Map<Integer, Integer>>, which is separate from Map<String, String>.
If TypeReference were non-abstract, you could write:
TypeReference<Map<String, String>> p = new TypeReference<>();
TypeReference<Map<Integer, Integer>> q = new TypeReference<>();
but the types of p and q would be indistinguishable.
Precisely to force you to create a subclass, because it's the only way to capture the generic type.
It's written in the very first sentence of the javadoc that you linked to:
This generic abstract class is used for obtaining full generics type information by sub-classing.
(emphasis mine).
The documentation also has a link to a blog post explaining that principle.
The question itself has already been answered, but i'd like to provide a solution to the problem of the code being ugly due to having to use curly braces and occupy an extra line.
Normally it's used like this:
Map<String, String> map = objectMapper.readValue(file, new TypeReference<>() {
});
It's not too bad, but it may be annoying to some. So, there is a way to avoid it by using a JavaType. You can create a utility method:
private <K, V> JavaType mapType(Class<K> keyClass, Class<V> valueClass) {
return objectMapper.getTypeFactory().constructParametricType(Map.class, keyClass, valueClass);
}
and use it like this:
Map<String, String> map = objectMapper.readValue(file, mapType(String.class, String.class));
Same can be done for Lists and other collections.
Related
I have a generic class Class1<K, V> which takes two types. Now I want to maintain a map which maps String to Class1 objects i.e Map<String, Class1> . Can I retrieve the types (K,V) of each Class1 Object while I iterate through Map?
i.e lets think of code as below
Map<String, Class1> map;
map.put("1", Class1<String, Integer> obj1);
map.put("2", Class1<String, Double> obj2);
map.put("3", Class1<Integer, Double> obj3);
Now when I iterate over the map ... Can I somehow get the types(K, V) of each Class1 object??
It's not possible according to JVM specification.
Generic types are available only at compile time. At runtime ther're only Object.
All you have is 2 options:
Add class definition variable to your objects and us it when you need this info.
E.g. for List<Object> as well as for other collections if it's not empty, you can get an item from it and get item.getClass().
P.S. Buy the way, your approach is not correct to solve this issue. You could implement an interface like:
interface TypeConverter {
boolean isValid(String str); // call it before to check if current converter valid for required transform or not
Class1 converts(String str); // call it to convert
}
And for each required types you have to create separate converter (this is Strategy Pattern). And your final code could look like this:
final TypeConverter converters = new ArrayList<>();
converters.add(new OneTypeConverter());
converters.add(new TwoTypeConverter());
public static Class1 convert(String str) {
for(TypeConverter convereter : converters)
if(converter.isValid(str))
return converter.convert(str);
throw new RuntimeException("No suitable converter found");
}
Consider my custom extended hashmap:
public class CustomHashMap extends HashMap<String, Object> {
...
}
Why doesn't this work since CustomHashMap is child of HashMap?
Map<String, HashMap<String, Object>> customs = new LinkedHashMap<String, CustomHashMap>();
But this works:
Map<String, HashMap<String, Object>> customs = new LinkedHashMap();
And also it works when adding (put) an CustomHashMap into the customs Map.
customs.put("test", new CustomHashMap());
It seems weird that not specifying the generics at initialization works, but it doesn't otherwise.
This statement is not working
Map<String, HashMap<String, Object>> customs = new LinkedHashMap<String, CustomHashMap>();
because customs is of type Map<String, HashMap<String, Object>> and you are assigning a LinkedHashMap which is of type <String, CustomHashMap>, where CustomHashMap is a sub class of HashMap<String, Object>.
Generics are invariant: for any two distinct types T1 and T2, HashMap<String, T1> is neither a subtype nor a supertype of HashMap<String, T2>. So, LinkedHashMap<String, CustomHashMap> cannot be assigned to Map<String, HashMap<String, Object>>. On the other hand, arrays are covariant, which means below statement will compile without any error or warning. But, it might fail at run time (which might cause more harm) if you put any other subtype of HashMap<String, Object> into it other than CustomHashMap :
HashMap<String, Object>[] mapArray = new CustomHashMap[1];
mapArray[0] = new CustomHashMap_1();// this will throw java.lang.ArrayStoreException
Now, if you want to assign LinkedHashMap<String, CustomHashMap> to Map<String, HashMap<String, Object>> , change the statement to this:
Map<String, ? extends HashMap<String, Object>> customs = new LinkedHashMap<String, CustomHashMap>();
Some additional information about this approach is nicely explained by #Seelenvirtuose , which is the accepted answer.
When working with generics, you should always keep type erasure in mind. At runtime an objct of type Map does not know its type parameters anymore. The consequence: A LinkedHashMap<String, CustomHashMap> is not a sub-type of Map<String, HashMap<String, Object>>.
If you want to have somthing sub-type related you must do it the following way:
Map<String, ? extends HashMap<String, Object>> customs = new LinkedHashMap<String, CustomHashMap>();
This is called an upper-bounded wildcard and exists exactly for that case: To get a sub-type relationship. Please refer to the Java tutorial about generics for more information.
An additional info as per the comment:
The upper-bounded version has a disadvantage on how to use the customs map. You cannot put instances anymore into that map. The only value allowed is null. The reason is, that you could have another class extending Map<String, HashMap> and try to put an instance of that into your customs map. But this is a problem, as the variable customs refers to a map that was parameterized with CustomHashMap.
When working with bounded wildcards, you should always remind PECS. PECS stands for "producer extends, consumer super". This is valuable for method parameters. If you write a method that only needs to read values from such a map, you could type the parameter as Map<String, ? extends Map<String, Object>>. This is called a producer. If you only need to write to that map, use the keyword super. If you need both - read and write - you cannot do either.
From the java tutorial on oracle's site
List<String> ls = new ArrayList<String>(); // 1
List<Object> lo = ls; // 2
Line 1 is certainly legal. The trickier part of the question is line 2. This boils down to the question: is a List of String a List of Object. Most people instinctively answer, "Sure!"
Well, take a look at the next few lines:
lo.add(new Object()); // 3
String s = ls.get(0); // 4: Attempts to assign an Object to a String!
Here we've aliased ls and lo. Accessing ls, a list of String, through the alias lo, we can insert arbitrary objects into it. As a result ls does not hold just Strings anymore, and when we try and get something out of it, we get a rude surprise.
The Java compiler will prevent this from happening of course. Line 2 will cause a compile time error.
this link would help you to learn generics and subtyping
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.
This question already has answers here:
How can I initialise a static Map?
(43 answers)
Closed 6 years ago.
I was just wondering if it is possible to define the contents of a Map Object on initialisation.
For example, an array can be created, as:
new String[] {“apples”, “bananas”, “pears”}
So, I was wondering if there is something similar we can do for maps.
You can, sort of, using this syntax trick:
Map<String,String> map = new HashMap<String,String>() {{
put("x", "y");
put("a", "b");
}};
Not very pleasant, though. This creates an anonymous subclass of HashMap, and populates it in the instance initializer.
If your Map is going to be immutable after creation and you don't mind adding a dependency, Guava offers some nice fluent syntax:
Map<K,V> aMap = ImmutableMap.<K,V>builder().put(key0, val0).put(key1,val1).build();
If you're feeling really exotic, Scala has syntax exactly like what you want and is interoperable with other Java code:
val aMap = Map("a"->0, "b"->1)
Note that the Scala compiler will infer the Map generic type is from String to Int, based on what you put in it, though you can explicitly specify it as well.
However, if this is just a one-off, I'd go with the initializer-based syntax. Both the Guava library and Scala language have a lot else to recommend them, but learning a whole new library/language might be overboard.
You can use initializer blocks:
class Foo {
//using static initializer block
static Map<String,String> m1 = new HashMap<String,String>();
static {
m1.put("x","y");
m1.put("a","b");
}
//using initializer block
Map<String,String> m2 = new HashMap<String,String>();
{
m2.put("x","y");
m2.put("a","b");
}
}
Something very hacky..can be improved, but this is just a direction:
Define a static helper to convert an object array to a map of this type:
public static<K,V> Map<K, V> fromArray(Object[] anObjArray){
int size = anObjArray.length;
Map<K, V> aMap = new HashMap<K, V>();
for (int i=0;i<=size/2;i=i+2){
K key = (K)anObjArray[i];
V value = (V)anObjArray[i+1];
aMap.put(key, value);
}
return aMap;
}
then you can create a map using this:
Map<Integer, String> aMap = MapUtils.<Integer, String>fromArray(new Object[]{1, "one", 2,"two"});
I would personally second Gauva builder suggestion from #Carl though :-)
I'm using Eclipse to help me clean up some code to use Java generics properly. Most of the time it's doing an excellent job of inferring types, but there are some cases where the inferred type has to be as generic as possible: Object. But Eclipse seems to be giving me an option to choose between a type of Object and a type of '?'.
So what's the difference between:
HashMap<String, ?> hash1;
and
HashMap<String, Object> hash2;
An instance of HashMap<String, String> matches Map<String, ?> but not Map<String, Object>. Say you want to write a method that accepts maps from Strings to anything: If you would write
public void foobar(Map<String, Object> ms) {
...
}
you can't supply a HashMap<String, String>. If you write
public void foobar(Map<String, ?> ms) {
...
}
it works!
A thing sometimes misunderstood in Java's generics is that List<String> is not a subtype of List<Object>. (But String[] is in fact a subtype of Object[], that's one of the reasons why generics and arrays don't mix well. (arrays in Java are covariant, generics are not, they are invariant)).
Sample:
If you'd like to write a method that accepts Lists of InputStreams and subtypes of InputStream, you'd write
public void foobar(List<? extends InputStream> ms) {
...
}
By the way: Joshua Bloch's Effective Java is an excellent resource when you'd like to understand the not so simple things in Java. (Your question above is also covered very well in the book.)
Another way to think about this problem is that
HashMap<String, ?> hash1;
is equivalent to
HashMap<String, ? extends Object> hash1;
Couple this knowledge with the "Get and Put Principle" in section (2.4) from Java Generics and Collections:
The Get and Put Principle: use an
extends wildcard when you only get
values out of a structure, use super
wildcard when you only put values into
a structure, and don't use a wildcard
when you both get and put.
and the wild card may start making more sense, hopefully.
It's easy to understand if you remember that Collection<Object> is just a generic collection that contains objects of type Object, but Collection<?> is a super type of all types of collections.
The answers above covariance cover most cases but miss one thing:
"?" is inclusive of "Object" in the class hierarchy. You could say that String is a type of Object and Object is a type of ?. Not everything matches Object, but everything matches ?.
int test1(List<?> l) {
return l.size();
}
int test2(List<Object> l) {
return l.size();
}
List<?> l1 = Lists.newArrayList();
List<Object> l2 = Lists.newArrayList();
test1(l1); // compiles because any list will work
test1(l2); // compiles because any list will work
test2(l1); // fails because a ? might not be an Object
test2(l2); // compiled because Object matches Object
You can't safely put anything into Map<String, ?>, because you don't know what type the values are supposed to be.
You can put any object into a Map<String, Object>, because the value is known to be an Object.
Declaring hash1 as a HashMap<String, ?> dictates that the variable hash1 can hold any HashMap that has a key of String and any type of value.
HashMap<String, ?> map;
map = new HashMap<String, Integer>();
map = new HashMap<String, Object>();
map = new HashMap<String, String>();
All of the above is valid, because the variable map can store any of those hash maps. That variable doesn't care what the Value type is, of the hashmap it holds.
Having a wildcard does not, however, let you put any type of object into your map. as a matter of fact, with the hash map above, you can't put anything into it using the map variable:
map.put("A", new Integer(0));
map.put("B", new Object());
map.put("C", "Some String");
All of the above method calls will result in a compile-time error because Java doesn't know what the Value type of the HashMap inside map is.
You can still get a value out of the hash map. Although you "don't know the value's type," (because you don't know what type of hash map is inside your variable), you can say that everything is a subclass of Object and, so, whatever you get out of the map will be of the type Object:
HashMap<String, Integer> myMap = new HashMap<>();// This variable is used to put things into the map.
myMap.put("ABC", 10);
HashMap<String, ?> map = myMap;
Object output = map.get("ABC");// Valid code; Object is the superclass of everything, (including whatever is stored our hash map).
System.out.println(output);
The above block of code will print 10 to the console.
So, to finish off, use a HashMap with wildcards when you do not care (i.e., it does not matter) what the types of the HashMap are, for example:
public static void printHashMapSize(Map<?, ?> anyMap) {
// This code doesn't care what type of HashMap is inside anyMap.
System.out.println(anyMap.size());
}
Otherwise, specify the types that you need:
public void printAThroughZ(Map<Character, ?> anyCharacterMap) {
for (int i = 'A'; i <= 'Z'; i++)
System.out.println(anyCharacterMap.get((char) i));
}
In the above method, we'd need to know that the Map's key is a Character, otherwise, we wouldn't know what type to use to get values from it. All objects have a toString() method, however, so the map can have any type of object for its values. We can still print the values.