generic types on method parameters

generic-list, generics, java

Solution

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?

Problem

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!

Original source