Why doesn't this code work when wrapped in a generic function?

强颜欢笑 提交于 2021-02-05 06:59:26

问题


I use this code to perform a HTTP POST request and deserialize the returned value:

ParameterizedTypeReference<MyClass> typeRef = new ParameterizedTypeReference<>() {};
HttpEntity<Object> requestEntity = new HttpEntity<>("some text");
ResponseEntity<MyClass> result = restTemplate.exchange("/test", HttpMethod.POST, requestEntity, typeRef);
MyClass returnValue = result.getBody();

To make it easier to use, I tried to wrap the code in a function like so:

public <T> T post(Object content, Class<T> returnType, String url){
    ParameterizedTypeReference<T> typeRef = new ParameterizedTypeReference<>() {};
    HttpEntity<Object> requestEntity = new HttpEntity<>(content);
    ResponseEntity<T> response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, typeRef);
    return response.getBody();
}

However the code stops functioning when it's put in a function. It throws java.lang.ClassCastException: java.base/java.util.LinkedHashMap cannot be cast to client.rest.MyClass. It seems as though some type information is lost somewhere along the way.

Here's the complete code in form of 2 test cases:

package client.rest;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.*;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.client.support.RestGatewaySupport;

import static org.springframework.test.web.client.ExpectedCount.times;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;

class MyClass {
    public int getInt(){
        return 1;
    }

    public void setInt(int i){}
}

public class TEMP {

    public static RestTemplate restTemplate = new RestTemplate();
    public static MockRestServiceServer mockServer;

    @BeforeClass
    public static void beforeClass() throws JsonProcessingException {
        MyClass value = new MyClass();

        // set up a mock server
        RestGatewaySupport gateway = new RestGatewaySupport();
        gateway.setRestTemplate(restTemplate);
        mockServer = MockRestServiceServer.bindTo(gateway).build();

        ObjectMapper objectmapper = new ObjectMapper();
        String payload = objectmapper.writeValueAsString(value);

        mockServer.expect(times(2), requestTo("/test"))
            .andRespond(withSuccess(payload, MediaType.APPLICATION_JSON));
    }

    @Test
    public void without_function() {
        ParameterizedTypeReference<MyClass> typeRef = new ParameterizedTypeReference<>() {};
        HttpEntity<Object> requestEntity = new HttpEntity<>("some text");
        ResponseEntity<MyClass> result = restTemplate.exchange("/test", HttpMethod.POST, requestEntity, typeRef);
        MyClass returnValue = result.getBody();
    }

    public <T> T post(Object content, Class<T> returnType, String url){
        ParameterizedTypeReference<T> typeRef = new ParameterizedTypeReference<>() {};
        HttpEntity<Object> requestEntity = new HttpEntity<>(content);
        ResponseEntity<T> response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, typeRef);
        return response.getBody();
    }

    @Test
    public void with_function() {
        MyClass returnValue = post("some text", MyClass.class, "/test");
    }
}

My question is twofold:

  1. Why doesn't the function work?
  2. How can I make it work?

回答1:


Answer for 1.

ParameterizedTypeReference<X> typeRef = new ParameterizedTypeReference<X>() {};

Thanks the final {} jackson is able to find out the what X is in run-time using reflection however X is resolved in compilation time so if you have MyClass or T that is exactly what it will get in runtime; It won't be able to figure out what the T is assigned to in runtime.

For the very same reason, if you keep using the function-less option but you remove the {} at the end it will compile but it will result in the same error.

Answer for 2.

Instead of Class<T> returnType, that you never make reference to btw, you could pass ParameterizedTypeReference<T> typeRef directly. The code that call the post would then need to determine T in compilation time:

@Test
    public void with_function() {
        ParameterizedTypeReference<MyClass> typeRef = new ParameterizedTypeReference<>() {}; 
        MyClass returnValue = post("some text", typeRef, "/test");
    }
}

However I think you should consider alternatives that do not rely on the {} trick which might be problematic.

Have you tried ParameterizedTypeReference's forType?:

public <T> T post(Object content, Class<T> returnType, String url){
        ParameterizedTypeReference<T> typeRef = ParameterizedTypeReference.forType(returnType);
        HttpEntity<Object> requestEntity = new HttpEntity<>(content);
        ResponseEntity<T> response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, typeRef);
        return response.getBody();
}

In any case this will work with non-generic assignations to T like MyClass as when passing MyClass.class as the return type; It would not work with ArrayList<MyClass> list; list.getClass() since it would be equivalent to return ArrayList.class. I guess in those cases you would need to construct and pass a different Type instance that would correspond to the more complex type expression.




回答2:


You don't need a ParameterizedTypeReference because you don't have a parameterized type. Just use the overload that accepts a Class directly:

ResponseEntity<T> response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, returnType);


来源:https://stackoverflow.com/questions/47974703/why-doesnt-this-code-work-when-wrapped-in-a-generic-function

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!