I have such a method:
private static <T extends HomerMessage> HomerMessage postRequest(String path, HomerMessage json) throws IOException, HomerDoh
{
RequestBody body = RequestBody.create(JSON, toJson(json));
Request request = new Request.Builder().url("http://" + path).post(body).build();
String response = new OkHttpClient().newCall(request).execute().body().string();
System.out.println(response);
JsonNode responseNode = new ObjectMapper().readValue(response, JsonNode.class);
if(!"200".equals(responseNode.get("status")))
{
throw readData(response, new TypeReference<HomerDoh>() {});
}
return readData(response, new TypeReference<T>() {});
}
private static <T> T readData(String is, TypeReference<T> ref) throws IOException
{
return mapper.readValue(is, ref);
}
All works fine, but I could not figure out how to call it...
I have tried:
AuthResponse ar = HomerClient.postRequest(url + "/api/v1/session", auth);
The last expression does not compile.
How to call parametrized method in Java?
AuthResponse extends HomerMessage
Your code doesn't compile because it's not returning an AuthResponse: it is returning a HomerMessage.
You can make the return type AuthResponse if you change the return type of the method to:
private static <T extends HomerMessage> T postRequest(
String path, HomerMessage json, TypeReference<T> typeRef)
throws IOException, HomerDoh
which is the return type of your only normally-completing code path.
As noted by #SLaks, you can't use TypeReference with generics:
new TypeReference<T>() {}
Because of erasure, this will be equivalent at runtime to:
new TypeReference<Object>() {}
which is almost certainly not what you want - otherwise you could just have used that, and not had an issue calling the generic method in the first place.
You need to actually pass in the concrete TypeReference as a parameter:
private static <T extends HomerMessage> T postRequest(
String path, HomerMessage json, TypeReference<T> typeRef)
throws IOException, HomerDoh
then you can call this simply as:
AuthResponse response = postRequest(
url + "/api/v1/session", auth,
new TypeReference<AuthResponse>() {});
and the type T is inferred from the third parameter.
You need to provide the name of the class, where the static method is defined:
Exception ar = SomeClass.<Exception>getException();
The expression Exception ar = <Exception>getException(); cannot compile because the generic method parametrization idiom requires a class name (or variable name if the method wasn't static) prior to parametrization.
For instance:
Exception ar = MyClass.<Exception>getException();
For instance methods:
Exception ar = theObject.<Exception>getException();
Exception ar = this.<Exception>getException();
Related
I have data structure that looks more or less like this
class ResponseWrapper<T> {
T response;
public ResponseWrapper(T response) {
this.response = response;
}
}
And service that handles reading that response from JSON to actual DTO.
public class GenericService<T> {
public ResponseWrapper<T> read(String json, Class<T> clazz) throws Exception {
T response = new ObjectMapper().readValue(json, clazz);
return new ResponseWrapper<>(response);
}
}
And I can call it like this:
GenericResponse<SomeData> response = new GenericService<SomeData>().read("json value", SomeData.class)
And what I'm trying to achieve is:
GenericResponse<SomeData> response = new GenericService<SomeData>().read("json value")
And I'm wondering, is it actually possible? This is obviously not working
public ResponseWrapper<T> read(String json) throws Exception {
T response = new ObjectMapper().readValue(json, T.class);
return new ResponseWrapper<>(response);
}
No. It is not possible.
Java generics work by type erasure ... and that means that the actual class associated with generic type parameter is not available at runtime. If your code needs to know that class, you need to pass a Class object explicitly.
And, yes, T.class is a compilation error.
And, yes, there is no way to get the class of T.
Consider this External execution class
public class ExternalCommands {
private Logger log = LoggerFactory.getLogger(ExternalCommands.class);
private static final ObjectMapper objectMapper = new ObjectMapper();
public <T> CustomResponse<T> executeQuery(Clients client, Query query, Class<T> classType) throws Exception {
if (Objects.isNull(clients))
throw new Exception("external client is null in external commands");
log.debug("Query : {}", query);
Response queryResponse = clients.getElasticClient().executeQuery(query);
log.debug("query response : {}", queryResponse);
if (queryResponse.status() == 200) {
JavaType javaType = objectMapper.getTypeFactory().constructParametricType(CustomResponse.class, classType); // This is the coding sin m talking about
return objectMapper.readValue(queryResponse.body().asInputStream(), javaType);
}
}
}
So Basically this executeQuery function fetches all the data as per query from an elastic client and deserialise it as per the generic classType as sent in function BUT
this is more like explicitly doing the deserialisation instead of using Generics.
See below code on how this execution works along with inline comments :
public ArrayList<EmpData> getEmpData() throws Exception {
ArrayList<EmpData> merchantUrnMap = new ArrayList<>();
List<Filter> filters = new ArrayList<>();
filters.add("Added filter 1 here");
filters.add("Added filter 2 here");
filters.add("Added filter 3 here");
ExternalCommands commands = new ExternalCommands();
Query query = commands.getQuery(filters);
// get "clients"
// this is how it works now
CustomResponse<EmpData> response = commands.executeQuery(clients, query, EmpData.class);
// this is how i WANT IT TO WORK - without passing "EmpData.class"
// but in this case <T> in "CustomResponse<T>" would not deserialise to "EmpData"
// resulting in LinkedHashMap in deseralised object instead of class object
// CustomResponse<EmpData> response = commands.<EmpData>executeQuery(clients, query);
// some operations
return response
}
any suggestions on how to achieve this?
Case 1: assuming that constructParametricType requires the classType argument to function properly, and you can't change the implementation of methods/classes that are implied but not provided in your posted code.
Your proposed method signature/invocation not possible due to type erasure in Java.
You use classType in the constructParametricType(CustomResponse.class, classType) call, and you're trying to replace classType with T somehow. This is impossible, because when the code is compiled, the T is erased completely. There is no way to do something like constructParametricType(CustomResponse.class, T.class) because T doesn't exist at runtime.
The correct solution is to pass in the class as a method argument, which is precisely what your existing approach does.
Case 2: you really want to have the call commands.<EmpData>executeQuery(clients, query); and you're willing to change anything to achieve that goal.
Since we cannot pass T as an argument to constructParametricType, it must be called as constructParametricType(CustomResponse.class), yet it needs to return a JavaType representing CustomResponse<T>. The only way to do that is to declare
<T> JavaType<T> constructParametricType(Class<?> cls)
Note that JavaType now also has to be parameterized for the same reason (we can't get T at runtime). Finally, we have to declare
CustomResponse<T> readValue(InputStream stream, JavaType<T> javaType)
to match the declared return type of executeQuery.
After all of these changes, the line
CustomResponse<EmpData> response = commands.<EmpData>executeQuery(clients, query);
should compile. Here's a minimal example:
class CustomResponse<T> {}
class Clients{}
class Query{}
class EmpData{}
class ObjectMapper {
JavaTypeFactory getTypeFactory() {
return new JavaTypeFactory();
}
<T> CustomResponse<T> readValue(InputStream s, JavaType<T> j) {
return new CustomResponse<>();
}
}
class JavaTypeFactory {
<T> JavaType<T> constructParametricType(Class<?> cls) {
return new JavaType<>(cls);
}
}
class JavaType<T> {
JavaType(Class<?> cls) {}
}
class ExternalCommands {
private static final ObjectMapper objectMapper = new ObjectMapper();
public <T> CustomResponse<T> executeQuery(Clients clients, Query query) throws Exception {
InputStream queryResponseStream = null;
JavaType<T> javaType = objectMapper.getTypeFactory().<T>constructParametricType(CustomResponse.class);
return objectMapper.readValue(queryResponseStream, javaType);
}
}
class SomeClass {
public void getEmpData() throws Exception {
ExternalCommands commands = new ExternalCommands();
Query query = null;
Clients clients = null;
CustomResponse<EmpData> response = commands.<EmpData>executeQuery(clients, query);
}
}
Beware that some of the described changes might not be easy/possible given the rest of your system (especially parameterizing JavaType), and I don't recommend this approach. I recommend sticking with what you have; it's the cleanest approach IMO.
I receive the name of the function to be used as biPredicate at runtime. I want to pass around this biPredicate and evaluate, basically filter to get results.
Following is my utility that defines biPredicates. I tried utilizing MethodHandle and Lambda Functions. When I use
new FilterUtility().execute("genericFilter");
I get java.lang.AbstractMethodError
public class FilterUtility {
public void execute(String filterName) throws Throwable {
ActualBean beanObject = ActualBean.builder().param1("true").param2("false").build();
MethodType methodType = MethodType.methodType(boolean.class, Object.class, Object.class);
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle handle = lookup.findStatic(FilterUtility.class, filterName, methodType);
BiPredicate<Object, Object> f = (BiPredicate<Object, Object>) LambdaMetafactory.metafactory(lookup,
"test",
MethodType.methodType(BiPredicate.class),
methodType.generic(),
handle,
methodType)
.getTarget()
.invokeExact();
resolve(beanObject, new HashMap<>(), f);
}
public static <SourceObject, TemplateObject> Map<String, String> resolve(SourceObject src,
TemplateObject template,
BiPredicate<SourceObject, TemplateObject> p) {
if (p.test(src, template))
return new HashMap<>();
return null;
}
public static <SourceObject, TemplateObject> boolean genericFilter(SourceObject x, TemplateObject y) {
ObjectMapper ob = new ObjectMapper();
Map<String, Object> source = ob.convertValue(x, Map.class);
Map<String, Object> template = ob.convertValue(y, Map.class);
for (Map.Entry<String, Object> entry : template.entrySet()) {
if (!source.get(entry.getKey()).equals(entry.getValue()))
return false;
}
return true;
}
}
When I change implementation of execute to following, I dont get exceptions.
public void execute(String filterName) throws Throwable {
ActualBean beanObject = ActualBean.builder().param1("true").param2("false").build();
resolve(beanObject, new HashMap<>(), FilterUtility::genericFilter); }
This makes me believe that there is something wrong with the way I am trying to find the function with name and send it as a biPredicate.
You are calling the method methodType.generic() which will replace all parameter types and the return type with java.lang.Object, including primitive types. So you are transforming the target method’s signature (Object,Object)->boolean to (Object,Object)->Object, effectively creating a class with a method Object test(Object,Object) which will invoke your target method and box the result.
Such type mismatches are not checked by the lambda metafactory. So when you try to invoke BiPredicate’s method boolean test(Object,Object) on that generated class, an error will be thrown.
The correct method to use is methodType.erase(), which will replace all reference types with java.lang.Object but keep primitive types as-is. Though, in this specific case, you don’t need to transform the method type at all, as the target method’s type is already (Object,Object)->boolean, so just replacing methodType.generic() with methodType would work too.
Some of my server response for success are:
{
"error": null,
"data": null
}
My model is:
public class BaseResponse<E > {
#Json(name = "error")
public ErrorModel error;
#Json(name = "data")
public E data;
}
If my endpoint is
#POST("user")
Call<BaseResponse<String>> createUser(#Body Credentials credentials);
This works, but the type String is useless as data will always be null. But if i make it:
#POST("user")
Call<BaseResponse> createUser(#Body Credentials credentials);
I got crash at
Call<BaseResponse> call = apiService.createUser(creds);
full log:
java.lang.IllegalArgumentException: Unable to create converter for
class common.model.responses.BaseResponse for method MyEndpoints.createUser
...
Caused by: java.lang.IllegalArgumentException: Expected a Class, ParameterizedType,
or GenericArrayType, but <null> is of type null
at com.squareup.moshi.Types.getRawType(Types.java:167)
at com.squareup.moshi.ClassJsonAdapter$1.createFieldBindings(ClassJsonAdapter.java:83)
at com.squareup.moshi.ClassJsonAdapter$1.create(ClassJsonAdapter.java:75)
at com.squareup.moshi.Moshi.adapter(Moshi.java:100)
at com.squareup.moshi.ClassJsonAdapter$1.createFieldBindings(ClassJsonAdapter.java:91)
at com.squareup.moshi.ClassJsonAdapter$1.create(ClassJsonAdapter.java:75)
at com.squareup.moshi.Moshi.adapter(Moshi.java:100)
at retrofit2.converter.moshi.MoshiConverterFactory.responseBodyConverter(MoshiConverterFactory.java:91)
at retrofit2.Retrofit.nextResponseBodyConverter(Retrofit.java:330)
at retrofit2.Retrofit.responseBodyConverter(Retrofit.java:313)
at retrofit2.ServiceMethod$Builder.createResponseConverter(ServiceMethod.java:736)
...
This specific error surfaces because there is no way to know the type of the E field, since it was not specified in the raw type usage.
Since you know that this field will always be null, the best solution is to use a different type (that lacks the field) to clarify this.
If you really cannot use a different type, you could use Void as the generic type argument. You would need to register a JsonAdapter on your Moshi instance for the Void field to be deserialized, though: new Moshi.Builder().add(VOID_JSON_ADAPTER).build()
static final Object VOID_JSON_ADAPTER = new Object() {
#FromJson Void fromJson(JsonReader reader) throws IOException {
return reader.nextNull();
}
#ToJson void toJson(JsonWriter writer, Void v) throws IOException {
writer.nullValue();
}
};
But, using a different type makes the most sense.
Certainly I'm quite new in all this Java stuff, so I have a question, I'm trying to deserialize a response obtained on a WCF service, everything works fine, but, I'm trying to make a generic function to do this.
Basically what I do is
public List<msg> GetService(String method){
List<msg> response = new ArrayList<msg>();
Type msgType = new TypeToken<List<msg>>(){}.getType();
//Obtaining result
response = uJSON.fromJson(serviceResponse, msgType);
//uJSON is an instance of Gson library, for deserializing it just needs
//the service response and a Class<T> or Type to reflect the obtained message
}
What I'm trying to do is obtaining the Type "msg" generic, it means...
public <thing> void GetInstanceService(String method){
List<thing> response = new ArrayList<thing>();
Type rType2 = new TypeToken<List<thing>>(){}.getType(); //Got java.util.List<thing>
//And when I'm trying to deserialize I just obtain a List of object
//[java.lang.Object#5c7a987e, java.lang.Object#74b1a7a0]
type2 = uJSON.fromJson(new String(entity), rType2);
}
But I'm calling like this.
comm.<msgType>GetInstanceService("listTestType");
So, when I call "GetInstanceService", "thing" is "msgType" Type, for the
List<thing> and also response shouldn't be List<msgType> instead of List <Object>?
Besides, when I'm trying to explicitly pass the type through a "Type" parameter, it just causes me compilation time error like this.
public void GetInstanceService(Type type){
List<type> type2 = new ArrayList<type>(); //Compilation time error
//Or
msgType oType = new msgType();
Class classType = oType.getClass();
List<classType> type3; //Compilation time error
}
So, if none of these attempts was effective, how could I set the type for deserialization?
Guava class TypeToken does not support that mode of usage. You are creating the type token with a type variable and there not enough information for it to reconstruct List<String> from List<T>. You should create an instance of TypeToken where you have all the required compile-time information.
The documentation says:
Note that it's critical that the actual type argument is carried by a
subclass. The following code is wrong because it only captures the <T>
type variable of the listType() method signature; while <String> is
lost in erasure:
class Util {
static <T> TypeToken<List<T>> listType() {
return new TypeToken<List<T>>() {};
}
}
TypeToken<List<String>> stringListType = Util.<String>listType();
But as said above, you can instantiate the TypeToken at call-site, where all type info are available, and then pass it as a parameter. Something like this:
public <thing> void GetInstanceService(String method, TypeToken<List<thing>> token){
List<thing> response = new ArrayList<thing>();
Type rType2 = token.getType();
type2 = uJSON.fromJson(new String(entity), rType2);
}
comm.GetInstanceService("listTestType", new TypeToken<List<msgType>>() {});
Update
Paul Bellora notes that you can also accept a parameter TypeToken<thing> token, and construct a TypeToken<List<thing>> inside the method from that token:
public <thing> void GetInstanceService(String method, TypeToken<thing> token) {
List<thing> response = new ArrayList<thing>();
Type rType2 = new TypeToken<List<thing>>() {}
.where(new TypeParameter<thing>() {}, token); // where() binds "thing" to token
.getType();
type2 = uJSON.fromJson(new String(entity), rType2);
}
comm.GetInstanceService("listTestType", new TypeToken<msgType>() {});
Due to something called type erasure, the class object you need is not available at runtime.
However, there is a standard work-around: pass a type token into your method, like this:
public <T> List<T> getService(String method, Class<T> c) {
// the caller has passed in the class object
List<T> list = new ArrayList<T>();
// fill list
return list;
}