Java: 如何 return 泛型
Java: How to return a generic type
我们有一个调用各种 rest 端点的服务,并将 JSON 响应转换为一个对象。
我们已经在 java 中看到,该服务可以 return 通用类型,但无法弄清楚语法。
假设我们有一堆用于不同 API 响应的模型,以及一种调用端点的方法和 return 其中之一。例如
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 有一个内置的方法来执行此操作,而不是 return 将响应作为字符串,但我们不能使用它有两个原因:
- 我们需要记录原始响应字符串(这必须在任何可能失败的 json 对象映射之前完成)
- 它的“REST”,所以 returns 状态代码 400、404 等导致异常并且没有映射,但我们仍然需要读取 json repose 并将其转换为对象(在本例中包含错误字段)
作为Yuliya Sheludyakova ,你不能做new T();
因为javac
由于泛型擦除而缺少类型信息。即使此时已知类型和构造函数也不需要创建一个新对象,因为 Gson returns a new 反序列化对象(Yuliya Sheludyakova 也提到了这一点) .
你可以做的是向 fromJson
方法调用提供类型(java.lang.reflect.Type
的实例,java.lang.Class
是其中之一),以便 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 和其他库做同样的事情,可能值得在您的代码中使用这些库。
我们有一个调用各种 rest 端点的服务,并将 JSON 响应转换为一个对象。
我们已经在 java 中看到,该服务可以 return 通用类型,但无法弄清楚语法。
假设我们有一堆用于不同 API 响应的模型,以及一种调用端点的方法和 return 其中之一。例如
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 有一个内置的方法来执行此操作,而不是 return 将响应作为字符串,但我们不能使用它有两个原因:
- 我们需要记录原始响应字符串(这必须在任何可能失败的 json 对象映射之前完成)
- 它的“REST”,所以 returns 状态代码 400、404 等导致异常并且没有映射,但我们仍然需要读取 json repose 并将其转换为对象(在本例中包含错误字段)
作为Yuliya Sheludyakova new T();
因为javac
由于泛型擦除而缺少类型信息。即使此时已知类型和构造函数也不需要创建一个新对象,因为 Gson returns a new 反序列化对象(Yuliya Sheludyakova 也提到了这一点) .
你可以做的是向 fromJson
方法调用提供类型(java.lang.reflect.Type
的实例,java.lang.Class
是其中之一),以便 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 和其他库做同样的事情,可能值得在您的代码中使用这些库。