Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic method, equality constraint

Tags:

java

generics

Hello Im trying to implement a generic method as controller base method, but the problem which I cannot understand happens with the generic method signature.

<T> ResponseEntity<T> makeApiCall(String path, HttpMethod httpMethod, T body, boolean isAdmin){

        String sender = isAdmin ? adminHash : userHash;
        HttpHeaders headers = new HttpHeaders();
        headers.add("Authorization", sender);
        headers.add("Content-Type", "application/json");
        HttpEntity<T> entity = new HttpEntity<>(body,headers);
        ResponseEntity<T> responseEntity = restTemplate.exchange(path, HttpMethod.POST, entity, body.getClass());
        return responseEntity;
}

The compile error I currently have is as follows:

Incompatible equality constraint: T and capture of ? extends Object
like image 258
filemonczyk Avatar asked Feb 18 '17 16:02

filemonczyk


2 Answers

You don't say exactly where the problem occurs, but I think this will occur on the restTemplate.exchange( call, as a result of passing body.getClass() as a parameter. This is because the return type of body.getClass() is Class<? extends Object>, as in the Javadoc:

The actual result type is Class<? extends |X|> where |X| is the erasure of the static type of the expression on which getClass is called

The problem is that you can't guarantee that body is specifically a T - it could be a subclass of T. As such, the result of body.getClass() might not be a Class<T>.

If you want to be type-safe, you would need to pass that in as an additional parameter to the method.

<T> ResponseEntity<T> makeApiCall(
    String path, HttpMethod httpMethod, T body, Class<T> bodyClass, 
    boolean isAdmin){
  // ...
  ResponseEntity<T> responseEntity =
      restTemplate.exchange(
          path, HttpMethod.POST, entity, bodyClass);
  // ...
}

Note that the only way to obtain a Class<T> is to use a class literal, e.g. String.class if T is String. This precludes the use of generic body types, since there are no generic class literals.

like image 72
Andy Turner Avatar answered Oct 18 '22 03:10

Andy Turner


You need to cast body.getClass() to Class<T>

@SuppressWarnings("unchecked")
<T> ResponseEntity<T> makeApiCall(String path, HttpMethod httpMethod, T body, boolean isAdmin){

    String sender = isAdmin ? adminHash : userHash;

    HttpHeaders headers = new HttpHeaders();
    headers.add("Authorization", sender);
    headers.add("Content-Type", "application/json");
    HttpEntity<T> entity = new HttpEntity<>(body,headers);
    ResponseEntity<T> responseEntity = restTemplate.exchange(path, HttpMethod.POST, entity, (Class<T>) body.getClass());
    return responseEntity;
}

To make it type-safe you need to pass the class object as a parameter explicitly:

<T> ResponseEntity<T> makeApiCall(String path, HttpMethod httpMethod, T body, Class<T> clazz, boolean isAdmin){
String sender = isAdmin ? adminHash : userHash;

    HttpHeaders headers = new HttpHeaders();
    headers.add("Authorization", sender);
    headers.add("Content-Type", "application/json");
    HttpEntity<T> entity = new HttpEntity<>(body,headers);
    ResponseEntity<T> responseEntity = restTemplate.exchange(path, HttpMethod.POST, entity, clazz);
    return responseEntity;
}
like image 21
medvedev1088 Avatar answered Oct 18 '22 02:10

medvedev1088