Sorry for the yet another "Java generic method is not applicable" question. I would like to know what am I missing in my understanding:
List<E> is a subtype of Collection<E>
--> meaning, List<String> is a subtype of Collection<String>
Suppose A extends B, List<A> is not a subtype of List<B>
--> but in this case, there's only one type T (or String), so I don't see how the Substitution Principle can explain my problem?
Problem Code:
private <T, K> void genericAddToMapOfLists(HashMap<K, Collection<T>> mapOfLists,
K key, T value) {
if (mapOfLists.containsKey(key)) {
mapOfLists.get(key).add(value);
} else {
List<T> newList = new ArrayList<T>();
newList.add(value);
mapOfLists.put(key, newList);
}
}
private void parseToFruitList(HashMap<String, List<String>> fruit_colors,
String fruitName) {
String color = "";
genericAddToMapOfLists(fruit_colors, fruitName, color);
}
Error:
The method genericAddToMapOfLists(HashMap<K,Collection<T>>, K, T) in the type MyGroceryStore is not applicable for the arguments (HashMap<String,List<String>>, String, String)
The code works when I change the method signature to genericAddToMapOfLists(HashMap<K,List<T>>, K, T).
This is exactly the problem you are explaining in your second point.
Suppose A extends B, List<A> is not a subtype of List<B>
In this case your method expects
HashMap<?, Collection<?>>
but you are giving it
HashMap<?, List<?>>
List extends Collection, but HashMap<?, List> is not a subtype of HashMap<?, Collection>
(I'm not using ? as a wildcard, we just don't care about it right now)
You're right in that "List<String> is a subtype of Collection<String>". And if A extends B, List<A> is not a subtype of List<B>.
Taking that one step further, a HashMap<String, List<String>> is not a HashMap<String, Collection<String>>.
The same reasoning applies, where A is List and B is Collection. If a HashMap<String, List<String>> was a HashMap<String, Collection<String>>, then you could put a Vector<String> into a HashMap<String, List<String>> by assigning it to a HashMap<String, Collection<String>>, even though a Vector isn't a List, and so it's not allowed.
Related
How come one must use the generic type Map<?, ? extends List<?>> instead of a simpler Map<?, List<?>> for the following test() method?
public static void main(String[] args) {
Map<Integer, List<String>> mappy =
new HashMap<Integer, List<String>>();
test(mappy);
}
public static void test(Map<?, ? extends List<?>> m) {}
// Doesn't compile
// public static void test(Map<?, List<?>> m) {}
Noting that the following works, and that the three methods have the same erased type anyways.
public static <E> void test(Map<?, List<E>> m) {}
Fundamentally, List<List<?>> and List<? extends List<?>> have distinct type arguments.
It's actually the case that one is a subtype of the other, but first let's learn more about what they mean individually.
Understanding semantic differences
Generally speaking, the wildcard ? represents some "missing information". It means "there was a type argument here once, but we don't know what it is anymore". And because we don't know what it is, restrictions are imposed on how we can use anything that refers to that particular type argument.
For the moment, let's simplify the example by using List instead of Map.
A List<List<?>> holds any kind of List with any type argument. So i.e.:
List<List<?>> theAnyList = new ArrayList<List<?>>();
// we can do this
theAnyList.add( new ArrayList<String>() );
theAnyList.add( new LinkedList<Integer>() );
List<?> typeInfoLost = theAnyList.get(0);
// but we are prevented from doing this
typeInfoLost.add( new Integer(1) );
We can put any List in theAnyList, but by doing so we have lost knowledge of their elements.
When we use ? extends, the List holds some specific subtype of List, but we don't know what it is anymore. So i.e.:
List<? extends List<Float>> theNotSureList =
new ArrayList<ArrayList<Float>>();
// we can still use its elements
// because we know they store Float
List<Float> aFloatList = theNotSureList.get(0);
aFloatList.add( new Float(1.0f) );
// but we are prevented from doing this
theNotSureList.add( new LinkedList<Float>() );
It's no longer safe to add anything to the theNotSureList, because we don't know the actual type of its elements. (Was it originally a List<LinkedList<Float>>? Or a List<Vector<Float>>? We don't know.)
We can put these together and have a List<? extends List<?>>. We don't know what type of List it has in it anymore, and we don't know the element type of those Lists either. So i.e.:
List<? extends List<?>> theReallyNotSureList;
// these are fine
theReallyNotSureList = theAnyList;
theReallyNotSureList = theNotSureList;
// but we are prevented from doing this
theReallyNotSureList.add( new Vector<Float>() );
// as well as this
theReallyNotSureList.get(0).add( "a String" );
We've lost information both about theReallyNotSureList, as well as the element type of the Lists inside it.
(But you may note that we can assign any kind of List holding Lists to it...)
So to break it down:
// ┌ applies to the "outer" List
// ▼
List<? extends List<?>>
// ▲
// └ applies to the "inner" List
The Map works the same way, it just has more type parameters:
// ┌ Map K argument
// │ ┌ Map V argument
// ▼ ▼
Map<?, ? extends List<?>>
// ▲
// └ List E argument
Why ? extends is necessary
You may know that "concrete" generic types have invariance, that is, List<Dog> is not a subtype of List<Animal> even if class Dog extends Animal. Instead, the wildcard is how we have covariance, that is, List<Dog> is a subtype of List<? extends Animal>.
// Dog is a subtype of Animal
class Animal {}
class Dog extends Animal {}
// List<Dog> is a subtype of List<? extends Animal>
List<? extends Animal> a = new ArrayList<Dog>();
// all parameterized Lists are subtypes of List<?>
List<?> b = a;
So applying these ideas to a nested List:
List<String> is a subtype of List<?> but List<List<String>> is not a subtype of List<List<?>>. As shown before, this prevents us from compromising type safety by adding wrong elements to the List.
List<List<String>> is a subtype of List<? extends List<?>>, because the bounded wildcard allows covariance. That is, ? extends allows the fact that List<String> is a subtype of List<?> to be considered.
List<? extends List<?>> is in fact a shared supertype:
List<? extends List<?>>
╱ ╲
List<List<?>> List<List<String>>
In review
Map<Integer, List<String>> accepts only List<String> as a value.
Map<?, List<?>> accepts any List as a value.
Map<Integer, List<String>> and Map<?, List<?>> are distinct types which have separate semantics.
One cannot be converted to the other, to prevent us from doing modifications in an unsafe way.
Map<?, ? extends List<?>> is a shared supertype which imposes safe restrictions:
Map<?, ? extends List<?>>
╱ ╲
Map<?, List<?>> Map<Integer, List<String>>
How the generic method works
By using a type parameter on the method, we can assert that List has some concrete type.
static <E> void test(Map<?, List<E>> m) {}
This particular declaration requires that all Lists in the Map have the same element type. We don't know what that type actually is, but we can use it in an abstract manner. This allows us to perform "blind" operations.
For example, this kind of declaration might be useful for some kind of accumulation:
static <E> List<E> test(Map<?, List<E>> m) {
List<E> result = new ArrayList<E>();
for(List<E> value : m.values()) {
result.addAll(value);
}
return result;
}
We can't call put on m because we don't know what its key type is anymore. However, we can manipulate its values because we understand they are all List with the same element type.
Just for kicks
Another option which the question does not discuss is to have both a bounded wildcard and a generic type for the List:
static <E> void test(Map<?, ? extends List<E>> m) {}
We would be able to call it with something like a Map<Integer, ArrayList<String>>. This is the most permissive declaration, if we only cared about the type of E.
We can also use bounds to nest type parameters:
static <K, E, L extends List<E>> void(Map<K, L> m) {
for(K key : m.keySet()) {
L list = m.get(key);
for(E element : list) {
// ...
}
}
}
This is both permissive about what we can pass to it, as well as permissive about how we can manipulate m and everything in it.
See also
"Java Generics: What is PECS?" for the difference between ? extends and ? super.
JLS 4.10.2. Subtyping among Class and Interface Types and JLS 4.5.1. Type Arguments of Parameterized Types for entry points to the technical details of this answer.
This is because the subclassing rules for generics are slightly different from what you may expect. In particular if you have:
class A{}
class B extends A{}
then
List<B> is not a subclass of List<A>
It's explained in details here and the usage of the wildcard (the "?" character) is explained here.
I'm wondering why this code couldn't be compiled:
private static List<? super String> x() {
return null;
}
List<Object> l = x();
List is of type object so we can store everything in it. Who can explain?
List<Object> != List<? super String>
You can store everything on it.
l.add("a");
l.add(1);
l.add(new Object());
But you cannot assign a List<? super String> to a List<Object> because they aren't the same thing.
You can add a BigDecimal to a List<Object>, but you cannot add a BigDecimal to a List<? super String>.
The following class defines two methods, both of which intuitively have the same functionality. Each function is called with two lists of type List<? super Integer> and a boolean value which specifies which of those lists should be assigned to a local variable.
import java.util.List;
class Example {
void chooseList1(boolean choice, List<? super Integer> list1, List<? super Integer> list2) {
List<? super Integer> list;
if (choice)
list = list1;
else
list = list2;
}
void chooseList2(boolean choice, List<? super Integer> list1, List<? super Integer> list2) {
List<? super Integer> list = choice ? list1 : list2;
}
}
According to javac 1.7.0_45, chooseList1 is valid while chooseList2 is not. It complains:
java: incompatible types
required: java.util.List<? super java.lang.Integer>
found: java.util.List<capture#1 of ? extends java.lang.Object>
I know that the rules for finding the type of an expression containing the ternary operator (… ? … : …) are pretty complex, but as far as I understand them, it chooses the most specific type to which both the second and third arguments can be converted without an explicit cast. Here, this should be List<? super Integer> list1 but it isn't.
I'd like to see an explanation of why this isn't the case, preferably with a reference of the Java Language Specification and an intuitive explanation of what could go wrong if it wasn't prevented.
This answers applies to Java 7.
The Java Language Specification states the following about the conditional operator (? :)
Otherwise, the second and third operands are of types S1 and S2
respectively. Let T1 be the type that results from applying boxing
conversion to S1, and let T2 be the type that results from applying
boxing conversion to S2.
The type of the conditional expression is the result of applying
capture conversion (§5.1.10) to lub(T1, T2) (§15.12.2.7).
In the expression
List<? super Integer> list = choice ? list1 : list2;
T1 is List<capture#1? super Integer> and T2 is List<capture#2? super Integer>. Both of these have lower bounds.
This article goes into detail about how to calculate lub(T1, T2) (or join function). Let's take an example from there
<T> T pick(T a, T b) {
return null;
}
<C, A extends C, B extends C> C test(A a, B b) {
return pick(a, b); // inferred type: Object
}
void tryIt(List<? super Integer> list1, List<? super Integer> list2) {
test(list1, list2);
}
If you use an IDE and hover over test(list1, list2), you will notice the return type is
List<? extends Object>
This is the best that Java's type inference can do. if list1 was a List<Object> and list2 was a List<Number>, the only acceptable return type is List<? extends Object>. Because this case has to be covered, the method must always return that type.
Similarly in
List<? super Integer> list = choice ? list1 : list2;
The lub(T1, T2) is again List<? extends Object> and its capture conversion is List<capture#XX of ? extends Object>.
Finally, a reference of type List<capture#XX of ? extends Object> can not be assigned to a variable of type List<? super Integer> and so the compiler doesn't allow it.
Time goes by and Java changes. I am happy to inform you that since Java 8, probably due to the introduction of "target typing", Feuermurmels example compiles without a problem.
The current version of the relevant section of the JLS says:
Because reference conditional expressions can be poly expressions, they can "pass down" context to their operands.
...
It also allows use of extra information to improve type checking of generic method invocations. Prior to Java SE 8, this assignment was well-typed:
List<String> ls = Arrays.asList();
but this was not:
List<String> ls = ... ? Arrays.asList() : Arrays.asList("a","b");
The rules above allow both assignments to be considered well-typed.
It's also interesting to note that the following, derived from Sotirios Delimanolis's code does not compile:
void tryIt(List<? super Integer> list1, List<? super Integer> list2) {
List<? super Integer> l1 = list1 == list2 ? list1 : list2; // Works fine
List<? super Integer> l2 = test(list1, list2); // Error: Type mismatch
}
This suggests that the information available when calculating type lower bound on the return type of test is different from that of the type of the conditional operator. Why this is the case I have no idea, it could be an interesting question in itself.
I use jdk_1.8.0_25.
Here is the scenario:
public static <T> List<T> isTriggeredByBlackList(Map<String, T> params, Class<T> clz) {
System.out.println(clz.getName());
return null;
}
What I want is to pass either String or List<String> to this method.
When it comes to String, it works just fine:
Map<String, String> map1 = new HashMap<String, String>();
map1.put("11", "22");
isTriggeredByBlackList(map1, String.class);
But When I tried to pass a List<String>, it goes wrong:
Map<String, List<String>> map = new HashMap<String, List<String>>();
List<String> l = new ArrayList<String>();
l.add("11");
l.add("22");
map.put("1", l);
isTriggeredByBlackList(map, List.class); //compile error!
With compile error as below:
The method isTriggeredByBlackList(Map<String,T>, Class<T>) in the type CommonTest is not applicable for the arguments (Map<String,List<String>>, Class<List>)
What I need is to write just one method which is suitable to both String type as well as List<String> type.
Could anyone help me out? Thanks a lot!
Change the signature of your method:
public static <T> List<T> isTriggeredByBlackList(Map<String, ? extends T> params, Class<T> clz)
Why does this work?
The expression ? extends T just means that any type that is a subtype of T (and of course T itself) is accepted.
What is a wildcard?
So when method isTriggeredByBlackList is called like that:
isTriggeredByBlackList(map, List.class);
... T is specified to be a (raw) List type, and so the first parameter must be a Map<String, any-type-that-extends-raw-List>, which is true for Map<String, List<String>> (because List<String> is a subtype of (raw) List).
But why is a List a subtype of (raw) List?
Generics are tricky, because polymorphism does not work as expected (on the first sight). A List<String> is-NOT-a List<Object>, although String extends Object! So this won't work:
List<Object> objList = new ArrayList<String>(); // compile error
(Note: It's good that this is not possible, but that's another story)
But a List<String> IS-a (raw) List (and it is-a List<?>). So this works:
List rawList = new ArrayList<String>(); // just compiler warning
List<?> unknownList = new ArrayList<String>();
The reason is: Raw types are still supported to be backwards-compatible! Otherwise old code that does not support generics could not be used nowadays. So any instance of a concrete parametrized type (e.g. ArrayList<String>) can be assigned to a reference of its raw type (e.g. ArrayList) or super types (e.g. List)!
Why are raw types permitted?
Why can't we pass something like List<String>.class?
Because parameterized types have no exact runtime type representation!
Why is there no class literal for concrete parameterized types?
But I want to get a List<List<String>> as return value!
This is no problem! Just define the left side of your assignment to be a List<List<String>>, and java does the rest:
List<List<String>> l = isTriggeredByBlackList(map, List.class);
BUT! This only works, if you slightly modify your method declaration:
public static <T> List<T> isTriggeredByBlackList(
Map<String, ? extends T> params, Class<? super T> clz)
(Otherwise you can't pass the raw List.class as second argument).
If all this modifications and tweaks make sense depends on the task your method should fulfill! Is it only reading from params? How does it make use of the generic type arguments? What is the advantage of using generics here? etc.
Btw.: Methods that start with is... should return a boolean value. Consider renaming your method!
Btw 2.: The return type of your method is List<T>, so in case you're specifying T to be a List, you'll get a List<List>. Is this intended?
There are 3 related points here, with this code.
public static <T> List<T> isTriggeredByBlackList(Map<String, T> params, Class<T> clz) {
System.out.println(clz.getName());
return null;
}
1) The requirement is to support only String or List<String> as parameter in map value.
That is not possible to meet in single (Generic) method.
It will require overloaded methods (as Steve P. mentioned) instead of Generic.
2) If we relax that and use generic method. Then the above definition can be used as it is, if we change the map's type.
Map<String, List> m = new HashMap<String, List>(); // The Raw List.
isTriggeredByBlackList(m, List.class);
3) If the method signature is changed to:
static <T> List<T> isTriggeredByBlackList(Map<String, ? extends T> params, Class<T> clz)
As #isnot2bad has explained quite well, it accepts all Lists as in case of #2.
I have a question regarding generics:
Map<? super String, ? super String> mappa1 = new HashMap<Object,Object>();
with super it's possible to instantiate a HashMap<Object,Object> for a <? super String>.
However then you can add only objects which extends String ( in this case only String itself).
Why don't they forbid by compilation error as well as happens with the extends wildcard.
I mean if once created a Map <Object, Object> it's possible only to add Strings.. why not forcing to create a Map<String, String> in the first place? (like it happens with the extends wildcard)
Again I know the difference between super and extends concerning generics. I would like just to know the details I have aboved-mentioned.
Thanks in advance.
Let's use List instead of Map for brevity.
Essentially, practical meaning of extends and super can be defined as follows:
List<? extends T> means "a List you can get T from"
List<? super T> means "a List you can put T into"
Now you can see that there is nothing special about extends - behavior of extends and super is completely symmetric:
List<? extends Object> a = new ArrayList<String>(); // Valid, you can get an Object from List<String>
List<? extends String> b = new ArrayList<Object>(); // Invalid, there is no guarantee that List<Object> contains only Strings
List<? super String> a = new ArrayList<Object>(); // Valid, you can put a String into List<Object>
List<? super Object> b = new ArrayList<String>(); // Invalid, you cannot put arbitrary Object into List<String>
I think you are thrown off because you picked a collection type. Collections are rarely used as consumers and thus a lower bound (? super X) is not put on their element types. A more appropriate example is predicate.
Consider a method such as <E> List<E> filter(List<? extends E> p, Predicate<? super E> p). It will take a list l and a predicate p and return a new list containing all elements of l which satisfy p.
You could pass in a List<Integer> and a Predicate<Number> which is satisfied by all multiples of 2.5. The Predicate<Number> would become a Predicate<? super Integer>. If it did not, you could not invoke filter as follows.
List<Integer> x = filter(Arrays.asList(1,5,8,10), Predicates.multipleOf(2.5));
Map<? super String, ? super String> mappa1 = new HashMap<Object,Object>();
Since Java Generics are based on type erasure, with this line you didn't create a MashMap<Object,Object>. You just created an instance of the HashMap class; the type parameters get lost immediately after this line of code and all that stays is the type of your mappa1 variable, which doesn't even mention Object. The type of the new expression is assignment-compatible with the type of mappa1 so the compiler allows the assignment.
In general, the type parameters used with new are irrelevant and to address this issue, Java 7 has introduced the diamond operator <>. All that really matters is the type of mappa1, which is is Map<? super String, ? super String>; as far as the rest of your code is concerned, this is the type of the instantiated map.
The problem you're describing doesn't exist.
It is because your reference is declared as Map<? super String, ? super String>. But your actual object can hold any object since it's HashMap<Object,Object>
Map<? super String, ? super String> mappa1 = new HashMap<Object,Object>();
map1.put("", "");
//you can put only string to map1
//but you can do this
Map map2 = map1;
map2.put(23, 234);
the same can be described by a better example:
String a = "a".
a.length(); // legal.
Object b = a;
b.length() // compilation error