简体   繁体   中英

How to pass Class type of <T> in a generic method

I am trying to write a generic method so I can reuse it for multiple Request and response types. This is the non-generic method

 public ReservationBookingResponse invokeService(String endPoint, ReservationBookingRequest reservationBookingRequest, HttpServletRequest httpServletRequest) throws  APIException {

ResponseEntity<ReservationBookingResponse> response = null;
try {  
    String authToken = getAuthToken(restTemplate, httpServletRequest);
    HttpEntity<ReservationBookingRequest> entity = new HttpEntity<ReservationBookingRequest>(reservationBookingRequest, addAuthorizationHeader(authToken));
    response = restTemplate.postForEntity(endPoint, entity, ReservationBookingResponse.class);  

} catch (Exception e) {
    throw new  APIException(e);
}

return response.getBody();
}

This is the generic method I wrote

public default T invoke(String endPoint, K requestBean, HttpServletRequest httpServletRequest) throws  APIException {

    RestTemplate restTemplate=new RestTemplate();
    ResponseEntity<T> response = null;
    try {
            String authToken = getAuthToken(restTemplate, httpServletRequest);
            HttpEntity<K> entity   = new HttpEntity<K>(requestBean, addAuthorizationHeader(authToken));
        response = restTemplate.postForEntity(endPoint, entity, T);

    } catch (Exception e) {
        throw new  APIException(e);
    }
    return  response.getBody();
    }

Now the problem is in response = restTemplate.postForEntity(endPoint, entity, T); I am getting an exception "T cannot be resolved to a type" in the non generic(former) method response = restTemplate.postForEntity(endPoint, entity, ReservationBookingResponse.class); this works fine.

Now I can not do T.class as it is not allowed. Can someone please tell me how to pass Class<T> to the method.

This is what the postforEntity method expects

@Override
    public <T> ResponseEntity<T> postForEntity(String url, Object request, Class<T> responseType, Object... uriVariables)
            throws RestClientException {

You need to be given the Class<T> at some point by the caller.

The most straightforward way is to require the caller to provide the class when calling invoke . It is what Piotr Wilkin suggests and what you did in your answer. It however creates more boilerplate than needed, and there is a better option.

Since T is a generic of the whole object, you could provide the Class<T> to the constructor, like:

private final Class<T> type; // some prefer naming like clazz

public MyClassThatHasTheInvokeMethod(Class<T> type) { /* set the field(s) here */ }

Then, you can easily provide the type to any method call that needs Class<T> .

You can't.

Because of type erasure, the type T is basically not provided at runtime in any way. The only case in which you can ever retrieve a type T is in case of a non-generic class that inherits from a specific case of a generic class (ie Foo extends Bar<Something> ), in this case you can retrieve the Something .

In all other cases, unfortunately, you have to use boilerplate - pass the class object explicitly. If any of your constructor arguments is an object of type T , you can use .getClass() instead to avoid the boilerplate, otherwise you have to pass the class manually.

This is what I had to do to make to make it work, pass the class type as an argument from calling method.

Quick fix Solution -1

public  T postRequestToService(String endPoint, K requestBean, HttpServletRequest httpServletRequest, 
 Class<T> classType) throws  APIException {

 response = restTemplate.postForEntity(endPoint, entity, classType);

and in the calling method

postRequestToService(endPoint+addOnAvailability, addOnAvailabilityRequest,
httpServletRequest, AddOnAvailabilityResponse.class);

However a better solution is below one - Solution -2

Define a constructor, even though this happens to be an abstract class

public class PegasusService<K, T> {

    private Class<K> requestClass;
    private Class<T> responseClass;

     public PegasusService(Class<K> requestClass, Class<T> responseClass) {
this.requestClass = requestClass;
this.responseClass = responseClass;
}

and in the child class pass the class types in the constructor.

   AddOnAvailabilityService() {
    super(AddOnAvailabilityRequest.class, AddOnAvailabilityResponse.class);
    }

So this code works fine response = restTemplate.postForEntity(endPoint, entity, responseClass); No need to pass the class type.

This way we dont have to add an extra argument or change the method definition.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM