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;
}
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 I have a simple class that uses generics.
public class ResponseWorkerRunnable<Parameter, Result> implements Runnable {
private final ResponseWorker<Parameter, Result> worker;
/**
* The parameters for {#link ResponseWorker#doInBackground(Object...)}
*/
private final Parameter[] params;
public ResponseWorkerRunnable(ResponseWorker<Parameter, Result> worker,
Parameter... params) {
uiThreadHandler = new Handler(Looper.getMainLooper());
this.worker = worker;
this.params = params;
}
#Override
public void run() {
try {
Result res = worker.doInBackground(params);
postResultBack(res);
} catch (Exception e) {
postErrorBack(e);
}
}
}
and my ResponseWorker:
public interface ResponseWorker<Parameter, Result> {
public Result doInBackground(Parameter... param) throws Exception;
}
The problem is, that I get ClassCastException:
java.lang.ClassCastException: java.lang.Object[] cannot be cast to
model.Table[]
I do something like this:
Table t = new Table();
ResponseWorker<Table, SuperTable> worker = ... ;
ResponseWorkerRunnable<Table, SuperTable> r = new ResponseWorkerRunnable<Table, SuperTable>(worker, t);
Than the ResponseWorkerRunnable will be scheduled and will run in the future with this exception:
java.lang.ClassCastException: java.lang.Object[] cannot be cast to
model.Table[]
at this line in the ResponseWorkerRunnable run() method:
Result res = worker.doInBackground(params);
I have used the debugger to check the Parameter[] params field (in ResponseWorkerRunnable) and its set to Object[1]{Table#4237c0e0}
So its an array of object but ResponseWorker.doInBackground expects an Array of class Table.
How do I cast it correctly?
Result res = worker.doInBackground((Parameter[]) params);
Any other idea or hint what could be wrong?
------ UPDATE -------
I use a singleton class called ResponseWorkerExecutor schedule the ResponseWorkerRunnable (with a ThreadPool) to
class ResponseWorkerExecutor {
public static <Parameter, Result> Future<?> submit(
ResponseWorker<Parameter, Result> responseWorker, Parameter ... params) {
return INSTANCE.executor
.submit(new ResponseWorkerRunnable<Parameter, Result>(
responseWorker, params));
}
}
So in my code I do something like this:
I do something like this:
Table t = new Table();
// ResponseWorker implementation
ResponseWorker<Table, SuperTable> worker = ... ;
// Here is the problem with the args
ResponseWorkerExecutor.submit(worker, t);
Due to the way generics work in Java (read here about type erasure) the actual Parameter class is being replaced by Object in the resulting bytecode, this is why your varargs array is Object[] and not Table[].
There is a workaround in this case that should work, it involves some changes to your code:
// Pass the actual class object to your Runnable, in this case t.getClass() -> Table.class
ResponseWorkerRunnable<Table, SuperTable> r = new ResponseWorkerRunnable<Table, SuperTable>(worker, t, t.getClass());
And then:
public class ResponseWorkerRunnable<Parameter, Result> implements Runnable {
private final ResponseWorker<Parameter, Result> worker;
/**
* The parameters for {#link ResponseWorker#doInBackground(Object...)}
*/
private final Parameter[] params;
private final Class<?> actualClass;
public ResponseWorkerRunnable(ResponseWorker<Parameter, Result> worker, Parameter... params, Class<?> actualClass) {
uiThreadHandler = new Handler(Looper.getMainLooper());
this.worker = worker;
this.params = params;
this.actualClass = actualClass;
}
#Override
public void run() {
try {
Result res = worker.doInBackground((Parameter[]) Arrays.copyOf(params, params.length, actualClass));
postResultBack(res);
} catch (Exception e) {
postErrorBack(e);
}
}
}
What this does is take the Object[] and copying its contents into a new, real Parameter[], whatever the actual class Parameter refers to. Then it makes the varargs call using this new array.
Using "Parameters" instead of the conventional "P" makes your code harder to read. What is happening is this. The type of this.params is correctly set to Parameter[]. If you passed a value to the constructor, then it would also be checked against Parameter[]. However, you didn't pass an argument, so the runtime creates an empty array for you. Unfortunately, it isn't smart enough to recognize the now erased type Parameter, so it creates an Object[]. I don't know if it should or not, but it isn't.
I understand what you are doing, and it makes sense. One way to "fix" the problem inside the constructor is to check the type of "params". Given that it is an array, you may not be able to use instanceof. Or, you can simply check to see if it empty. If you didn't receive a Parameter[], ignore "params" and create a new, emtpy Parameter[] and assign it to "this.params".
Use, that fixes it (at least thats what my Eclipse said ;-) )
public Result doInBackground(Parameter[] param);
If that fixed it, there seems to be a problem with the varags declaration and generics.
So I have sold this problem with a workaround. I use List instead of Parameter[] or Parameter ... params .
There are already some help methods in java.util.Collections class like: Collections.singletonList(param);
So in my case that seems to me the best solution, since I have only a single line of code, where I have to put a single object in a List<> or to convert a array to a list. Hence this method is part of a little library the user of the library does not have to take care about it.
The solution with Arrays.copyOf(params, params.length, actualClass)); suggested by #gpeche needs an aditional Class as parameter, and at the end the user of the library have to add the class.
So I guess I found a compromise by using List instead of Parameter ... params
I have a private instance
private final Map<Class<?>, ?> map;
Syntactically, this is correct. What I want to do is this.
public class User {
}
public class UserSubclass extends User {
}
public class Role {
}
map.put(User.class, new User()); // valid
map.put(User.class, new UserSubclass()); // valid
map.put(Role.class, new Role()); // valid
// But when I do the following I need to get an error
map.put(User.class, new Role(); // invalid, compiler error
How should I declare the Map?
How can I instantiate an object of HashMap to this Map?
No, a simple java.util.Map does not support this. It is statically typed, and what you ask for is basically dynamic typing of one parameter based on the runtime-type of another one.
However, the Guava class ClassToInstanceMap implements exactly what you want:
A map, each entry of which maps a Java raw type to an instance of that type. In addition to implementing Map, the additional type-safe operations putInstance(java.lang.Class<T>, T) and getInstance(java.lang.Class<T>) are available.
You cannot do this by default, but what you can do, is to create your own Generic Safe Map, which will work.
The GenericMap would look like this:
class GenericMap extends HashMap<Class<?>, Object> {
public <T> T putClassAndInstance(Class<T> c, T o){
return c.cast(super.put(c, o));
}
public <T> T getInstance(Class<T> c) {
return c.cast(super.get(c));
}
}
And can then be used like:
GenericMap map = new GenericMap();
map.putClassAndInstance(User.class, new User()); // valid
map.putClassAndInstance(User.class, new UserSubclass()); // valid
map.putClassAndInstance(Role.class, new Role()); // valid
map.putClassAndInstance(User.class, new Role()); // invalid, compiler error
This way, you don't have to create special methods for the User and Role, and still have the safety of not adding the wrong object for the wrong type.
Using Jersey I'm defining a service like:
#Path("/studentIds")
public void writeList(JsonArray<Long> studentIds){
//iterate over studentIds and save them
}
Where JsonArray is:
public class JsonArray<T> extends ArrayList<T> {
public JsonArray(String v) throws IOException {
ObjectMapper objectMapper = new ObjectMapper(new MappingJsonFactory());
TypeReference<ArrayList<T>> typeRef = new TypeReference<ArrayList<T>>() {};
ArrayList<T> list = objectMapper.readValue(v, typeRef);
for (T x : list) {
this.add((T) x);
}
}
}
This works just fine, but when I do something more complicated:
#Path("/studentIds")
public void writeList(JsonArray<TypeIdentifier> studentIds){
//iterate over studentIds and save them by type
}
Where the Bean is a simple POJO such as
public class TypeIdentifier {
private String type;
private Long id;
//getters/setters
}
The whole thing breaks horribly. It converts everything to LinkedHashMap instead of the actual object. I can get it to work if I manually create a class like:
public class JsonArrayTypeIdentifier extends ArrayList<TypeIdentifier> {
public JsonArrayTypeIdentifier(String v) throws IOException {
ObjectMapper objectMapper = new ObjectMapper(new MappingJsonFactory());
TypeReference<ArrayList<TypeIdentifier>> typeRef = new TypeReference<ArrayList<TypeIdentifier>>(){};
ArrayList<TypeIdentifier> list = objectMapper.readValue(v, typeRef);
for(TypeIdentifier x : list){
this.add((TypeIdentifier) x);
}
}
}
But I'm trying to keep this nice and generic without adding extra classes all over. Any leads on why this is happening with the generic version only?
First of all, it works with Longs because that is sort of native type, and as such default binding for JSON integral numbers.
But as to why generic type information is not properly passed: this is most likely due to problems with the way JAX-RS API passes type to MessageBodyReaders and MessageBodyWriters -- passing java.lang.reflect.Type is not (unfortunately!) enough to pass actual generic declarations (for more info on this, read this blog entry).
One easy work-around is to create helper types like:
class MyTypeIdentifierArray extends JsonArray<TypeIdentifier> { }
and use that type -- things will "just work", since super-type generic information is always retained.