Java:如何返回泛型类型

时间:2021-04-12 21:27:25

标签: java generics gson

我们有一个服务,它调用各种休息端点,并将 JSON 响应转换为一个对象。

我们已经看到它在java中完成,服务可以返回一个泛型类型,但无法弄清楚语法。

假设我们有一堆用于不同 API 响应的模型,以及一种调用端点并返回其中一个的方法。例如

public class MyServiceImpl implements MyService{
    @Override
    public <T> T doGet( String endpoint) { 
        :
        HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
        :
        Gson gson = new Gson();
        T model = new T();
        model = gson.fromJson(response.getBody(), model.class));
        return(model);
    }

并因此调用:

SomeModel model = myService.doGet("https://somesite.com/someendpoint")

显然,上面的代码行不通,因为你不能做“new T()”

httpClient 有一个内置的方法来做到这一点,而不是将响应作为字符串返回,但我们不能使用它有两个原因:

  1. 我们需要记录原始响应字符串(这必须在任何可能失败的 json 对象映射之前完成)
  2. 它的“REST”,因此返回 400、404 等导致异常且没有映射的状态代码,但我们仍然需要读取 json repose 并将其转换为对象(对于这种情况,其中包含错误字段) )

1 个答案:

答案 0 :(得分:1)

作为 Yuliya Sheludyakova mentioned,您不能执行 new T();,因为 javac 由于泛型擦除而缺少类型信息。即使那时已知类型和构造函数,也不需要创建新对象,因为 Gson 在反序列化时返回一个 new 对象(Yuliya Sheludyakova 也提到了这一点)。

您可以做的是为 java.lang.reflect.Type 方法调用提供类型(java.lang.Class 的一个实例,fromJson 是其中一个具有类型限制功能的实例),以便 Gson 反序列化将有效负载转换为所提供类型的对象。

public interface IService {

    @Nullable
    <T> T doGet(@Nonnull URL url, @Nonnull Type type)
            throws IOException;

    @Nullable
    <T> T doGet(@Nonnull URL url, @Nonnull TypeToken<? extends T> typeToken)
            throws IOException;

}
final class Service
        implements IService {

    // Gson instances are thread-safe and may be expensive on instantiation
    private static final Gson gson = new Gson();

    // Unsafe: the return type T and Type type are not bound to each other
    @Nullable
    @Override
    public <T> T doGet(@Nonnull final URL url, @Nonnull final Type type)
            throws IOException {
        // Prefer not using string buffers that may be very expensive for large payloads
        // Streams are much cheaper
        try ( final JsonReader jsonReader = new JsonReader(new InputStreamReader(doGet(url))) ) {
            return gson.fromJson(jsonReader, type);
        }
    }

    // Safe: javac can detect if the return type and the type token are bound
    @Nullable
    @Override
    public <T> T doGet(@Nonnull final URL url, @Nonnull final TypeToken<? extends T> typeToken)
            throws IOException {
        // Prefer not using string buffers that may be very expensive for large payloads
        // Streams are much cheaper
        try ( final JsonReader jsonReader = new JsonReader(new InputStreamReader(doGet(url))) ) {
            // TypeToken.getType() is guaranteed to provide a correct bound type
            return gson.fromJson(jsonReader, typeToken.getType());
        }
    }

    private static <T> T doGet(@Nonnull final URL url) {
        throw new AssertionError("Stub! " + url);
    }

}

安全方法的使用示例:

private static final TypeToken<List<User>> userListTypeToken = new TypeToken<List<User>>() {};

public static void main(final String... args)
        throws IOException {
    final IService service = new Service();
    final List<User> users = service.doGet(new URL("http://localhost:8080/users"), userListTypeToken);
    for ( final User user : users ) {
        System.out.println(user);
    }
}

请注意,Spring RestTemplate、Retrofit 和其他库的作用相同,可能值得在您的代码中使用这些库。