Question

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!

Was it helpful?

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?

OTHER TIPS

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top