Using 'or' in Java Generics declaration - java

I have a method that returns an instance of
Map<String, List<Foo>> x();
and another method that returns an instance of
Map<String, Collection<Foo>> y();
Now if I want to dynamically add one of this Maps in my field, how can I write the generics for it to work?
ie:
public class Bar {
private Map<String, ? extends Collection<Foo>> myMap;
public void initializer() {
if(notImportant) myMap = x(); //OK
else myMap = y(); // !OK (Need cast to (Map<String, ? extends Collection<Foo>>)
}
}
Now is it ok that I cast to the signature even though the y() is declared as being Collection?
If it is not ok to cast, can I somehow write this (Collection OR List)
I mean, List is a Collection, so it should somehow be possible.
private Map<String, Collection<Foo> | List<Foo>>> myMap;

The way you did it with ? extends Collection is fine. You can't have something like OR since if you did you wouldn't know what it is you're getting back if you do myMap.get("someString"); you can't do List|Collection someVariable = myMap.get("someString"), you have to choose one, and if you choose Collection it's the same as using ? extends, if you choose List, you'll end up in all sort of trouble if the object in the map is actually a Set (which is also a collection), not a list, and you try calling methods that only List has (like indexOf). As for the reason you need to use ? extends is because Map<String, List> does not extend Map<String, Collection> even though List extends Collection.
You should take note though, that using ? extends Collection will only let you get values from the map, since then it's sure that what you get is a Collection (or child of Collection), but if you try to put something in the map, you won't be able to (since myMap may be Map<String, Set>, but since you only see it as Map<String, ? extends Collection> you might try to put a List in it which wouldn't be ok)

I'm not sure what your problem is. This code (essentially your code) compiles just fine.
import java.util.*;
public class Generic {
static class Foo {};
static Map<String, List<Foo>> x() {
return null;
}
static Map<String, Collection<Foo>> y() {
return null;
}
static Map<String, ? extends Collection<Foo>> myMap;
public static void main(String[] args) {
myMap = x();
myMap = y();
myMap = new HashMap<String,SortedSet<Foo>>();
for (Collection<Foo> value : myMap.values());
}
}
You can NOT, however, do something like List<Integer|String>. Java generics type bounds just doesn't work like that.

Related

How to get a reference to the `Class<T>` object for a generic class with specific type parameters?

For instance, I can have
Class c = Map.class;
But what if I need c to refer to the class Map<String, ValueClass>?
I want to do something which expresses
Class c = (Map<String, ValueClass>).class;
My use case is that I need to do this so that I can pass c to getConstructor
The spcecific type Map<String, ValueClass> share the same class object with Map, so you can not aquire a custom class object.
At run time, (Map<String, ValueClass>).class equals Map.class because of type erasure:
public static void main(String[] args) {
Map map = new HashMap();
Map<String, Integer> anotherMap = new HashMap<>();
System.out.println(map.getClass().equals(anotherMap.getClass())); // true
}
If you really want to get the static type as you require, you can force your way to it using casts (albeit through raw types):
Class<Map<String, Object>> cl = (Class) Map.class;
However, as pointed in other comments and other answer, that doesn't help you as, at runtime, your class.getConstructor() call will only see Map.class anyway.
Just consider the signature of getConstructor:
getConstructor(Class<?>... parameterTypes)
The implementation retrieves the constructor by comparing parameters (check the source of java.lang.Class.arrayContentsEq(Object[], Object[])).
To summarize, it boils down to comparing (Class) cl == Map.class, and that returns true at runtime, which explains why it's no use looking for the generic class instance.
The value of Class<Map<String, Object>> cl would only be effectual if that were used for static type checking, but in this case, it doesn't help.

Type mismatch involving sets and collections

I am trying to write a class that has a Map as a field. The field is as follows:
Map<String, Collection<String>> courses;
In the constructor, I have to have the field in the form:
Map<String, Set<String>;
without changing the field at all.
I am getting an error when I try to initialize the field with the set. Can someone tell me why or what to do without altering the original field?
Despite Set<String> is actually a subtype of Collection<String>, a Map<String, Set<String>> is not a subtype of Map<String, Collection<String>>. In fact, they are completely different types and you can't assign one to the other.
Luckily, the Map interface defines the putAll method, which has the following signature:
void putAll(Map<? extends K,? extends V> m)
This means that the putAll method accepts a map whose keys and values might be of types that are subtypes of its own key and value types, respectively.
So, in your example, you could do as follows:
public class YourClass {
private final Map<String, Collection<String>> courses = new HashMap<>();
public YourClass(Map<String, Set<String>> courses) {
this.courses.putAll(courses);
}
}
You only have to make sure that the courses attribute has been instantiated before invoking putAll on it.
I'm not sure what actual question is about, but...
code below is working because of Type erasure at Runtime
public class SimpleTest {
protected Map<String, ? extends Collection<String>> courses;
public SimpleTest(Map<String,Set<String>> setsMap)
{
courses = setsMap;
}
public static void main(String... args) {
Map<String, ? extends Collection<String>> setMap = new HashMap<String, Set<String>>();
SimpleTest stInstance = new SimpleTest((Map<String, Set<String>>) setMap);
String str1 = "Hi";
String str2 = "Hello";
Set<String> stringSet = new HashSet<>();
stringSet.add(str1);
List<String> stringList = new ArrayList<>();
stringList.add(str2);
((Map<String, Collection<String>>)setMap).put("set1", stringSet);
((Map<String, Collection<String>>)setMap).put("list1", stringList);
System.out.println("set1 class: " + stInstance.courses.get("set1").getClass().getName());
System.out.println("list1 class: " + stInstance.courses.get("list1").getClass().getName());
System.out.println("map content: " + stInstance.courses);
}
}
output is:
set1 class:java.util.HashSet
list1 class:java.util.ArrayList
map content:{list1=[Hello], set1=[Hi]}
PS. I do not recommend to use such "technique", at all.
But as experiment it is interesting and funny :-)

Java generic method call

Consider the following code:
public class MainClass {
public static void main(String[] args) {
ArrayList<HashMap<String, Integer>> collection = new ArrayList<>();
ArrayList<HashMap<String, Integer>> outCollecttion = <String, Integer, HashMap<String, Integer>, ArrayList<HashMap<String, Integer>>>doSomeWork(collection);
}
public static <V extends Object, U extends Object, K extends Map<V, U>, J extends Collection<K>> Collection<K> doSomeWork(J collection) {
Collection<K> result = new ArrayList<>();
for (K element : collection) {
result.add(element); //here is supposed other code, this is just example
}
return result;
}
}
I want to do some work on a generic collection that contains map of some generic types. I know that Java has hard time figuring complex generic expressions, so I put explicit types before method call:
<String, Integer, HashMap<String, Integer>, ArrayList<HashMap<Integer, String>>>doSomeWork(collection)
But compiler will not compile that. I do understand that it might have something to do with the fact that I'm trying to use generic type in generic type, but I don't know how to write this method without using casts and than deprecating warnings (I usually compile with -Xlint:unchecked flag).
First, when you want to explicitly provide the type arguments, you have to call the method from its class:
... = MainClass.<String, Integer, HashMap<String, Integer>, ArrayList<HashMap<String, Integer>>>doSomeWork(collection);
Second, your method returns Collection<K>, but the variable is declared ArrayList<...>. The following works for me:
Collection<HashMap<String, Integer>> outCollecttion = ...
You don't need to use generics in assignment.
Collection<HashMap<String, Integer>> outCollecttion = doSomeWork(collection);

How to get class with generics types in Java

I am trying to make a method call like this,
public class GenericsTest<T> {
public static <T> Map<String, T> createMap(Class<? extends Map<String, T>> clazz) {
return null;
}
public static void main(String[] argv) {
Map<String, Integer> result = createMap(TreeMap.class);
}
}
But I am getting this error,
<T>createMap(java.lang.Class<? extends java.util.Map<java.lang.String,T>>) in test.GenericsTest<T> cannot be applied to (java.lang.Class<java.util.TreeMap>)
How to fix this problem?
Map<String, Integer> instance = new TreeMap<String, Integer>();
#SuppressWarnings("unchecked")
Map<String, Integer> map =
createMap((Class<? extends Map<String, Integer>>)instance.getClass());
map.put("x", 1);
System.out.println("THIS IS x: " + map.get("x"));
This will appropriately print out 1. The implementation of the method is most likely
try
{
return clazz.newInstance();
}
catch (Exception e)
{
throw new RuntimeException(e);
}
A better implementation of their API would be for them to ask you for the type, T, and for them to give back a Map of their choosing instead of asking you for all of the details. Otherwise, as long as they are not filling in the Map with any data, you can instantiate a Map with the generic type argument yourself like so:
public static <T> Map<String, T> getMap()
{
return new TreeMap<String, T>();
}
You can then access that without a warning:
// note the lack of type arguments, which are inferred
Map<String, Integer> instance = getMap();
// alternatively, you could do it more explicitly:
// Map<String, Integer> instance = ClassName.<Integer>getMap();
There's really no reason for them to ask you for the Class type of your Map except to give you back an exact match to the implementation (e.g., if you stick in a HashMap, then you will get back a HashMap, and if you stick in a TreeMap, then you will get back a TreeMap). However, I suspect that the TreeMap will lose any Comparator that it was constructed with, and since that is an immutable (final) field of TreeMap, then you cannot fix that; that means that the Map is not the same in that case, nor is it likely to be what you want.
If they are filling in the Map with data, then it makes even less sense. You could always pass in an instance of a Map to fill, or have them return a Map that you can simply wrap (e.g., new TreeMap<String, Integer>(instance);), and they should know which Map offers the most utility to the data.

List<Map<String, String>> vs List<? extends Map<String, String>>

Is there any difference between
List<Map<String, String>>
and
List<? extends Map<String, String>>
?
If there is no difference, what is the benefit of using ? extends?
The difference is that, for example, a
List<HashMap<String,String>>
is a
List<? extends Map<String,String>>
but not a
List<Map<String,String>>
So:
void withWilds( List<? extends Map<String,String>> foo ){}
void noWilds( List<Map<String,String>> foo ){}
void main( String[] args ){
List<HashMap<String,String>> myMap;
withWilds( myMap ); // Works
noWilds( myMap ); // Compiler error
}
You would think a List of HashMaps should be a List of Maps, but there's a good reason why it isn't:
Suppose you could do:
List<HashMap<String,String>> hashMaps = new ArrayList<HashMap<String,String>>();
List<Map<String,String>> maps = hashMaps; // Won't compile,
// but imagine that it could
Map<String,String> aMap = Collections.singletonMap("foo","bar"); // Not a HashMap
maps.add( aMap ); // Perfectly legal (adding a Map to a List of Maps)
// But maps and hashMaps are the same object, so this should be the same as
hashMaps.add( aMap ); // Should be illegal (aMap is not a HashMap)
So this is why a List of HashMaps shouldn't be a List of Maps.
You cannot assign expressions with types such as List<NavigableMap<String,String>> to the first.
(If you want to know why you can't assign List<String> to List<Object> see a zillion other questions on SO.)
What I'm missing in the other answers is a reference to how this relates to co- and contravariance and sub- and supertypes (that is, polymorphism) in general and to Java in particular. This may be well understood by the OP, but just in case, here it goes:
Covariance
If you have a class Automobile, then Car and Truck are their subtypes. Any Car can be assigned to a variable of type Automobile, this is well-known in OO and is called polymorphism. Covariance refers to using this same principle in scenarios with generics or delegates. Java doesn't have delegates (yet), so the term applies only to generics.
I tend to think of covariance as standard polymorphism what you would expect to work without thinking, because:
List<Car> cars;
List<Automobile> automobiles = cars;
// You'd expect this to work because Car is-a Automobile, but
// throws inconvertible types compile error.
The reason of the error is, however, correct: List<Car> does not inherit from List<Automobile> and thus cannot be assigned to each other. Only the generic type parameters have an inherit relationship. One might think that the Java compiler simply isn't smart enough to properly understand your scenario there. However, you can help the compiler by giving him a hint:
List<Car> cars;
List<? extends Automobile> automobiles = cars; // no error
Contravariance
The reverse of co-variance is contravariance. Where in covariance the parameter types must have a subtype relationship, in contravariance they must have a supertype relationship. This can be considered as an inheritance upper-bound: any supertype is allowed up and including the specified type:
class AutoColorComparer implements Comparator<Automobile>
public int compare(Automobile a, Automobile b) {
// Return comparison of colors
}
This can be used with Collections.sort:
public static <T> void sort(List<T> list, Comparator<? super T> c)
// Which you can call like this, without errors:
List<Car> cars = getListFromSomewhere();
Collections.sort(cars, new AutoColorComparer());
You could even call it with a comparer that compares objects and use it with any type.
When to use contra or co-variance?
A bit OT perhaps, you didn't ask, but it helps understanding answering your question. In general, when you get something, use covariance and when you put something, use contravariance. This is best explained in an answer to Stack Overflow question How would contravariance be used in Java generics?.
So what is it then with List<? extends Map<String, String>>
You use extends, so the rules for covariance applies. Here you have a list of maps and each item you store in the list must be a Map<string, string> or derive from it. The statement List<Map<String, String>> cannot derive from Map, but must be a Map.
Hence, the following will work, because TreeMap inherits from Map:
List<Map<String, String>> mapList = new ArrayList<Map<String, String>>();
mapList.add(new TreeMap<String, String>());
but this will not:
List<? extends Map<String, String>> mapList = new ArrayList<? extends Map<String, String>>();
mapList.add(new TreeMap<String, String>());
and this will not work either, because it does not satisfy the covariance constraint:
List<? extends Map<String, String>> mapList = new ArrayList<? extends Map<String, String>>();
mapList.add(new ArrayList<String>()); // This is NOT allowed, List does not implement Map
What else?
This is probably obvious, but you may have already noted that using the extends keyword only applies to that parameter and not to the rest. I.e., the following will not compile:
List<? extends Map<String, String>> mapList = new List<? extends Map<String, String>>();
mapList.add(new TreeMap<String, Element>()) // This is NOT allowed
Suppose you want to allow any type in the map, with a key as string, you can use extend on each type parameter. I.e., suppose you process XML and you want to store AttrNode, Element etc in a map, you can do something like:
List<? extends Map<String, ? extends Node>> listOfMapsOfNodes = new...;
// Now you can do:
listOfMapsOfNodes.add(new TreeMap<Sting, Element>());
listOfMapsOfNodes.add(new TreeMap<Sting, CDATASection>());
Today, I have used this feature, so here's my very fresh real-life example. (I have changed class and method names to generic ones so they won't distract from the actual point.)
I have a method that's meant to accept a Set of A objects that I originally wrote with this signature:
void myMethod(Set<A> set)
But it want to actually call it with Sets of subclasses of A. But this is not allowed! (The reason for that is, myMethod could add objects to set that are of type A, but not of the subtype that set's objects are declared to be at the caller's site. So this could break the type system if it were possible.)
Now here come generics to the rescue, because it works as intended if I use this method signature instead:
<T extends A> void myMethod(Set<T> set)
or shorter, if you don't need to use the actual type in the method body:
void myMethod(Set<? extends A> set)
This way, set's type becomes a collection of objects of the actual subtype of A, so it becomes possible to use this with subclasses without endangering the type system.
As you mentioned, there could be two below versions of defining a List:
List<? extends Map<String, String>>
List<?>
2 is very open. It can hold any object type. This may not be useful in case you want to have a map of a given type. In case someone accidentally puts a different type of map, for example, Map<String, int>. Your consumer method might break.
In order to ensure that List can hold objects of a given type, Java generics introduced ? extends. So in #1, the List can hold any object which is derived from Map<String, String> type. Adding any other type of data would throw an exception.

Categories

Resources