【问题标题】:Why doesn't this code work when wrapped in a generic function?为什么此代码在包装在通用函数中时不起作用?
【发布时间】:2018-06-07 01:36:33
【问题描述】:

我使用此代码执行 HTTP POST 请求并反序列化返回值:

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();
}

但是,代码在放入函数时会停止运行。它抛出java.lang.ClassCastException: java.base/java.util.LinkedHashMap cannot be cast to client.rest.MyClass。似乎某些类型信息在此过程中丢失了。

以下是 2 个测试用例形式的完整代码:

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");
    }
}

我的问题有两个:

  1. 为什么该功能不起作用?
  2. 我怎样才能让它工作?

【问题讨论】:

  • 根据您的异常,您的地图至少有一个元素无法转换为您的类。这就是我们使用泛型的原因,将运行时错误更改为编译时间和类型安全。
  • 你从不使用returnTypeargument。您需要为该类型创建一个 ParameterizedTypeReference。 docs.spring.io/spring/docs/current/javadoc-api/org/…
  • @JBNizet 而不是 typeRef 将 returnType 传递给交换方法已经有效,但我想主要问题是;为什么 ParameterizedTypeReference 不能识别泛型。
  • @JBNizet 我不明白。为什么我必须使用returnType,为什么T 不够用?它们几乎是一样的,不是吗?能详细解释一下吗?
  • 因为泛型类型在 Java 中被删除了。 ParameterizedTypeReference 的全部意义在于通过将其子类化为匿名内部类并实例化它来捕获在创建它时传递的具体泛型类型:new ParameterizedTypeReference&lt;MyClass&gt;() {}。一旦你使这个通用化,就没有要捕获的具体类了。 T 被编译器擦除为 Object。因此,由于 JSON 解组,Jackson 不再知道它必须使用哪种类型。

标签: java spring function generics jackson


【解决方案1】:

答案为 1。

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

感谢最后的{}jackson 能够使用反射找出运行时X 的内容,但是X 在编译时得到解决,所以如果你有MyClassT,那正是它将在运行时得到什么;它无法确定T 在运行时分配给了什么。

出于同样的原因,如果您继续使用无功能选项但在最后删除{},它将编译但会导致相同的错误。

回答 2。

您可以直接传递ParameterizedTypeReference&lt;T&gt; typeRef,而不是Class&lt;T&gt; returnType,即您永远不会引用btw。然后调用帖子的代码需要在编译时确定T

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

但是,我认为您应该考虑不依赖 {} 技巧的替代方案,这可能会出现问题。

你试过ParameterizedTypeReferenceforType吗?:

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();
}

在任何情况下,这都适用于对T 的非泛型分配,例如MyClass,就像将MyClass.class 作为返回类型传递时一样;它不适用于ArrayList&lt;MyClass&gt; list; list.getClass(),因为它等同于返回ArrayList.class。我猜在这些情况下,您需要构造并传递一个不同的 Type 实例,该实例对应于更复杂的类型表达式。

【讨论】:

  • 感谢您的回答。我可以建议您在标记的重复问题中也提交此答案吗?那里的答案都没有提到ParameterizedTypeReference.forType 函数,所以你的答案在那里非常有价值。
  • @Rawing 你确认它有效吗?如果是的话,很高兴。
  • @Rawing 那是因为这仅适用于非参数化类型,您实际上并不需要ParameterizedTypeReference。由于区别而重新提出问题。
【解决方案2】:

您不需要ParameterizedTypeReference,因为您没有参数化类型。只需使用直接接受Classoverload

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

【讨论】:

  • 啊,你当然是对的。我使用 ParameterizedTypeReference 的原因是因为我实际上有 2 个 post 函数 - 如问题中所示,1 个带有 Class&lt;T&gt; 参数,1 个带有 ParameterizedTypeReference 参数。一个函数将 Class&lt;T&gt; 转换为 ParameterizedTypeReference 并将其传递给另一个函数。出于问题的目的,我将这两个函数合并为一个函数,对此感到抱歉!
猜你喜欢
  • 2013-01-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多