-1

After going through the following links i.e. link1 and link2, i am facing a challenge to provide a class literal as a parameter. Following is my code and i am getting compile error as follows:

class GenericService {
    public <T> List<T> serve(Class<T> clazz) {
        if(clazz == Set.class) {
            Set<String> inputs = new HashSet<>();
            return implement(inputs, clazz);
        } else if(clazz == Map.class) {
            Map<String, Object> inputs = new HashMap<>();
            return implement(inputs, clazz);
        }
    }
    
    public List<Set<String>> implement(Set<String> inputs, Type clazz) {
        //Based on clazz decide that the output will be of type 'List<Set<String>>'
        List<Set<String>> result = new ArrayList<>();
        //do something with inputs
        //populate result
        return result;
    }
    
    public List<Map<String, Object>> implement(Map<String, Object> inputs, Type clazz) {
        //Based on clazz decide that the output will be of type 'List<Map<String, Object>>' 
        List<Map<String, Object>> result = new ArrayList<>();
        //do something with inputs
        //populate result
        return result;
    }
}

Couple of things to be noticed:

  1. I can only pass List.class or Map.class instead of also specifying the generic type. Based on that the result type is determined (implementation details are not provided).
  2. Since, the result type is derived using class literal i am getting the following compile time errors.

Type mismatch: cannot convert from List<Set<String>> to List<T>

Type mismatch: cannot convert from List<Map<String,Object>> to List<T>

How can i get this to work ?

Community
  • 1
  • 1
Adithya
  • 2,923
  • 5
  • 33
  • 47
  • Make a MCVE. You have been around long enough to know this. – Michael Apr 08 '19 at 08:13
  • This can't be fully type-safe. So you might just have to cast: `ResponseEntity>> response = (ResponseEntity>>) gasRestClient.postEntity(uri, List.class, serviceRequestDTO);`. This will be an unchecked cast, so you'd have to live with that warning. – ernest_k Apr 08 '19 at 08:24

1 Answers1

1

First things first.

  1. I can only pass List.class or Map.class instead of also specifying the generic type. Based on that the result type is determined (implementation details are not provided).

You cannot specify the generic type in the class parameter because (Read this carefully) The generic type information is not present during the runtime. At runtime, you do not get List<String> or List<Integer> etc. At runtime, everything is just plain List.


A brief history on why it is like that.

Generics did not exist until Java 5. So when generics was introduced as a part of Java 5, the last thing they wanted to do was to tell the whole world to change from List to List<String> or List<Whatever_Object_They_Want>.

For example, let's say you are using a dependency which was compiled using Java 4, and a method in this dependency which returns just plain old List, you can still call that method using your code, which is compiled in Java 5. Only thing is you cannot cast it to any generic type.

Imagine if Java5 started supporting only generic types like List<> and not the plain old List. You would have to depend on all the vendors to provide the dependency which is compiled in Java5. This would cause chaos. So to avoid this, and to maintain backward compatibility with those using previous versions of java, the generic type information is stripped off when the code is compiled to byte code. This is called Type Erasure.

It is because of this reason, you cannot specify the generic type with class literals of any collection type.


Coming to your second point

  1. Since, the result type is derived using class literal i am getting the following compile time errors.

Type mismatch: cannot convert from List<Set<String>> to List<T>

Type mismatch: cannot convert from List<Map<String,Object>> to List<T>

If you go through the Generics documentation, the generic type List is different from List< Map< String, Object >>>. (That is why we have generics in Java. Eg., List<String> is different from List<Object>. You cannot cast List<String> to List<Object>)

However, since you want to return List<Map<>> in some cases and List<Set<>> in some other cases depending on the parameter, I suggest you to change the return type of the method serve to List<? extends Collection>

Using List<? extends Collection> as a return type/method argument means that this list can be a List of any of the subclasses of Collection. Please have a look at the hierarchy to get more information.

Hope this helped!

Teja
  • 1,236
  • 12
  • 27
  • I have changed my question as @Michael suggested. – Adithya Apr 08 '19 at 10:31
  • @Adithya Edited the answer accordingly. Hope this helps – Teja Apr 09 '19 at 05:59
  • i accepted this answer for the solution you gave for using `? extends Collection`. The problem otherwise is still there about not being able to specify a generic type for class literal. I am aware of the problem which you have highlighted. I was trying to know how can i get around it. – Adithya Apr 15 '19 at 07:57
  • @Adithya Thanks for accepting the answer. There is no way to define a generic type for class literal because Class Literal is a runtime object. And generics is applicable only during compile time i.e., the generic type information doesn't exist at runtime. Thus it's not applicable for runtime entities like Class literal. Hope this added some more clarity. – Teja Apr 26 '19 at 08:43
  • exactly my point. We can't specify hence, the issue. Thanks for clarifying. – Adithya May 27 '19 at 10:11