My problem is that I'm trying to instantiate a list with a parameter that have the same class as my function's argument :
public <A extends CommunicationObject> List<A> Myfunction(A myObject){
List<A> firstList;
//do something
}
When I call the function :
List<person> persons = Myfunction(person myObject);
The first list take A as CommunicationObject and this is not what I want.
I also tried to do this :
public <A extends CommunicationObject> List<A> Myfunction(A myObject){
List<myObject.getClass()> firstList;
//do something
}
but it is not allowed. Is there any chance that I can fix this ?
Update :
"person" is a subClass of "CumminicationObject". There is some attributes that exists in person and not in CommunicationObject. Actually this is just an example. What I'm trying to do is to convert a JSON to List"<"A">", and A can be "person" or other class that extends CommunicationObject.
The JSON contain the same attributes as the "A" class in List"<"A">".
In order to do the convertion, the parameter "A" in List"<"A">" have to be the same as my object Class in myfunction(A myObject).
Java uses type-erasure, which means your method declaration is somewhat pointless because there's no return type for the compiler to infer the type of A from.
This means that at runtime you've effectively got:
public void Myfunction(CommunicationObject myObject)
This method signature, when you think about it, is what its implementation would have to work with anyway and so your list should be a List<CommunicationObject>.
Following update to the question regarding de-serialization from JSON:
When de-serializing from JSON to a Java object you've got two choices:
declare the type to de-serialize into at the point of de-serialization like GSON does.
infer the type to de-serialize into within the JSON.
Due Java's type-erasure this is the only way to do this.
Related
I am trying to write a generic method for deserializing json into my model. My problem is that I don't know how to get Class from the generic type T. My code looks something like this (and doesn't compile this way)
public class JsonHelper {
public <T> T Deserialize(String json)
{
Gson gson = new Gson();
return gson.fromJson(json, Class<T>);
}
}
I tried something else, to get the type, but it throws an error I had the class as JsonHelper<T> and then tried this
Class<T> persistentClass = (Class<T>) ((ParameterizedType)getClass()
.getGenericSuperclass())
.getActualTypeArguments()[0];
The method signature looks like this
com.google.gson.Gson.fromJson(String json, Class<T> classOfT)
So, how can I translate along T so that when I call JsonHelper.Deserialize<MyObject>(json); I get an instance of the correct object?
You need to get a Class instance from somewhere. That is, your Deserialize() method needs to take a Class<T> as a parameter, just like the underlying fromJson() method.
Your method signature should look like Gson's:
<T> T Deserialize(String json, Class<T> type) ...
Your calls will look like this:
MyObject obj = helper.Deserialize(json, MyObject.class);
By the way, the convention to start method names with a lowercase letter is well established in Java.
Unfortunately, the way Java handles generics, you cannot get the class like you're asking. That's why Google's stuff asks specifically for the class as an argument. You'll have to modify your method signature to do the same.
Following snippet is self-explanatory enough. You can see that type information is not erased, but mapper doesn't get the type information. My guess is that jackson doesn't allow this, right ? If I pass TypeReference directly, it is deserialized properly.
public class AgentReq<T> extends TypeReference<AgentResponse<T>> {...}
mapper.readValue(reader, new AgentReq<Map<String, Set<Whatever>>>());
It also doesn't work if I do this :
public class AgentReq<T> {
public TypeReference<AgentResponse<T>> getTypeRef() {
return new TypeReference<AgentResponse<T>>() {};
}
}
mapper.readValue(reader, new AgentReq<Map<String, Set<Whatever>>>()).getTypeRef();
I'm using version 2.1.5.
EDIT: For future reference, do not underestimate the TypeReference constructor when resolving problems. There you can see directly whether it was able to retrieve type information. Btw the answer is NO, you can't extend TypeReference and expect it to work, you can't even override its getType() method and supply it with type information resolved from your class, because all you can get is getClass().getGenericSuperClass() ... You can't do getClass().getGenericClass()
You need to understand how a TypeReference works. For that we go into the source code
protected TypeReference()
{
Type superClass = getClass().getGenericSuperclass();
if (superClass instanceof Class<?>) { // sanity check, should never happen
throw new IllegalArgumentException("Internal error: TypeReference constructed without actual type information");
}
...
_type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
}
The Class#getGenericSuperclass() javadoc states
Returns the Type representing the direct superclass of the entity
(class, interface, primitive type or void) represented by this Class.
If the superclass is a parameterized type, the Type object returned
must accurately reflect the actual type parameters used in the source
code.
In other words, if we could do new TypeReference() (we can't, it's abstract), it would return the Class instance for the class Object. However, with anonymous classes (which extend from the type)
new TypeReference<String>(){}
the direct superclass of the instance created is the parameterized type TypeReference and according to the javadoc we should get a Type instance that accurately reflect the actual type parameters used in the source code:
TypeReference<String>
from which you can then get the parameterized type with getActualTypeArguments()[0]), returning String.
Let's take an example to visualize using anonymous class and using a sub-class
public class Subclass<T> extends TypeReference<AgentResponse<T>>{
public Subclass() {
System.out.println(getClass().getGenericSuperclass());
System.out.println(((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]);
}
}
Running
new Subclass<String>();
prints
com.fasterxml.jackson.core.type.TypeReference<Test.AgentResponse<T>>
Test.AgentResponse<T>
which fits the javadoc rules. Test.AgentResponse<T> is the actual parameterized type in the source code. Now, if instead, we had
new Subclass<String>(){}; // anonymous inner class
we get the result
Test.Subclass<java.lang.String>
class java.lang.String
which also fits the bill. The inner class now extends directly from Subclass which is parameterized with the argument String in the source code.
You will notice that, with the Subclass anonymous inner class, we've lost information about the AgentResponse generic type. This is unavoidable.
Note that
reader = new StringReader("{\"element\":{\"map-element\":[{\"name\":\"soto\", \"value\": 123}]}}");
obj = mapper.readValue(reader, new AgentReq<Map<String, Set<Whatever>>>());
will compile and run, but the type AgentReq<Map<String, Set<Whatever>>> will have been lost. Jackson will use default type to serializes the JSON. The element will be deserialized as an AgentResponse, while map-element will be deserialized as a Map and the JSON array as an ArrayList.
Say we have a function:
void doSomething(Class<?> clazz);
If I want to call it for a class Foo, I would call it this way:
doSomething(Foo.class);
How do I call doSomething() if my type is Foo<Boo> ?
I guess the question is what is the equivalent of Foo.class for Foo<Boo>?
Is that even possible?
-- Update ---
I'll explain more.
I have a typed bean :
class EventMessage<T>{
T payload;
String type;
}
An object of this type gets converted to a Json form (String), then put on a JMS Queue;
The consumer needs to de-jasonize it back from the String to it's original form.
The Json ObjectMapper needs to know the type to convert to.
Say my EventMessage payload was Offer type, then I want something like that:
EventMessage <Offer> offerEvent = jsonObjectMapper.readValue(jsonMsg,
EventMessage<Offer>.class)
Except that there is no such thing as EventMessage<Offer>.class.
The issue is that EventMessage is a typed class so the Json converter would have no idea how to resolve it without extra information about the payload type.
There is only one Foo class object. Foo<Boo> and Foo<Integer> are not different classes; they are the same class, and even if you could do Foo<Boo>.class it would be identical to Foo except for the compile-time type. But in the first example, you are not using the compile-time type anyway, since you are passing to a parameter of type Class<?>.
In your second example, you did not show the signature of readValue(), but I am assuming that it is something like <T> T readValue(JSONMessage, Class<T>). This is completely different from your first example. In this case, the compile-time type helps determine the T which is the return type. You can either manipulate the type of the Class argument:
EventMessage<Offer> offerEvent =
jsonObjectMapper.readValue(jsonMsg, (Class<EventMessage<Offer>>)
(Class<?>)EventMessage.class)
or just use the raw type, get the raw type back, and let it be implicitly converted to the parameterized type:
EventMessage<Offer> offerEvent =
jsonObjectMapper.readValue(jsonMsg, EventMessage.class)
Try this:
doSomething((Class<Foo<Boo>>)Foo.class)
Or this:
EventMessage<Offer> offerEvent = jsonObjectMapper.readValue(jsonMsg,
(Class<EventMessage<Offer>>) EventMessage.class);
.class is a language literal, not a field. Maybe you like to report a feature-request to java
I have the following setup of classes/interfaces.
Interface IFoobar
Class BaseClass which implements IFoobar and is abstract
Class ConcreteClassA which extends BaseClass
Class ConcreteClassB which extends BaseClass
Class ConcreteClassC which extends BaseClass
I have a method for which I need to pass instances of java.lang.Class for the above concrete classes. I am declaring it like so.
void doSomething(String id, Class<IFoobar> c)
However, when I try to compile, java complains with an error more or less like this:
doSomething(java.lang.String,java.lang.Class<IFoobar>) in javaclass cannot be applied to
(java.lang.String,java.lang.Class<ConcreteClassA>)
register("12345", ConcreteClassA.class);
^
1 error
I've only recently needed to use java.lang.Class to do things, so I am guessing I am missing something simple. I would have expected the typed class declaration to work like normal parameters in that the concrete classes are recognized as instances of the interface, but this apparently isn't the case.
I've tried various methods of casting, etc and either not had the desired results, or had code which isn't valid. So any ideas as to what I need to do would be appreciated.
Thanks in advance.
A variable of type Class<IFoobar> can only hold a reference to a Class<IFoobar> object, not a Class<ConcreteClassA> object.
Change Class<IFoobar> to Class<? extends IFoobar>.
This behaviour is not particularly sensible for Class objects. It is much more logical for collections, where it stops you doing this:
void method1(List<IFoobar> list)
{
list.add(new ConcreteClassA());
}
void method2()
{
List<ConcreteClassB> list = /* something */;
method1(list);
// oops! we put a ConcreteClassA inside a List<ConcreteClassB>
}
the only accepted value for Class<IFoobar> is IFooBar.class. If you want to accept IFooBar.class and all its subclasses, you should use Class<? extends IFooBar>
The error has nothing to do with "Class"
Simply if you use
ArrayList<Object> x = new ArrayList<String>();
You get error: incompatible types
because, though the String class is a subclass of Object, ArrayList<Object> is a different type than ArrayList<String>. That is the nature of Generics.
You can use
void doSomething(String id, Class c)
Following snippet is self-explanatory enough. You can see that type information is not erased, but mapper doesn't get the type information. My guess is that jackson doesn't allow this, right ? If I pass TypeReference directly, it is deserialized properly.
public class AgentReq<T> extends TypeReference<AgentResponse<T>> {...}
mapper.readValue(reader, new AgentReq<Map<String, Set<Whatever>>>());
It also doesn't work if I do this :
public class AgentReq<T> {
public TypeReference<AgentResponse<T>> getTypeRef() {
return new TypeReference<AgentResponse<T>>() {};
}
}
mapper.readValue(reader, new AgentReq<Map<String, Set<Whatever>>>()).getTypeRef();
I'm using version 2.1.5.
EDIT: For future reference, do not underestimate the TypeReference constructor when resolving problems. There you can see directly whether it was able to retrieve type information. Btw the answer is NO, you can't extend TypeReference and expect it to work, you can't even override its getType() method and supply it with type information resolved from your class, because all you can get is getClass().getGenericSuperClass() ... You can't do getClass().getGenericClass()
You need to understand how a TypeReference works. For that we go into the source code
protected TypeReference()
{
Type superClass = getClass().getGenericSuperclass();
if (superClass instanceof Class<?>) { // sanity check, should never happen
throw new IllegalArgumentException("Internal error: TypeReference constructed without actual type information");
}
...
_type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
}
The Class#getGenericSuperclass() javadoc states
Returns the Type representing the direct superclass of the entity
(class, interface, primitive type or void) represented by this Class.
If the superclass is a parameterized type, the Type object returned
must accurately reflect the actual type parameters used in the source
code.
In other words, if we could do new TypeReference() (we can't, it's abstract), it would return the Class instance for the class Object. However, with anonymous classes (which extend from the type)
new TypeReference<String>(){}
the direct superclass of the instance created is the parameterized type TypeReference and according to the javadoc we should get a Type instance that accurately reflect the actual type parameters used in the source code:
TypeReference<String>
from which you can then get the parameterized type with getActualTypeArguments()[0]), returning String.
Let's take an example to visualize using anonymous class and using a sub-class
public class Subclass<T> extends TypeReference<AgentResponse<T>>{
public Subclass() {
System.out.println(getClass().getGenericSuperclass());
System.out.println(((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]);
}
}
Running
new Subclass<String>();
prints
com.fasterxml.jackson.core.type.TypeReference<Test.AgentResponse<T>>
Test.AgentResponse<T>
which fits the javadoc rules. Test.AgentResponse<T> is the actual parameterized type in the source code. Now, if instead, we had
new Subclass<String>(){}; // anonymous inner class
we get the result
Test.Subclass<java.lang.String>
class java.lang.String
which also fits the bill. The inner class now extends directly from Subclass which is parameterized with the argument String in the source code.
You will notice that, with the Subclass anonymous inner class, we've lost information about the AgentResponse generic type. This is unavoidable.
Note that
reader = new StringReader("{\"element\":{\"map-element\":[{\"name\":\"soto\", \"value\": 123}]}}");
obj = mapper.readValue(reader, new AgentReq<Map<String, Set<Whatever>>>());
will compile and run, but the type AgentReq<Map<String, Set<Whatever>>> will have been lost. Jackson will use default type to serializes the JSON. The element will be deserialized as an AgentResponse, while map-element will be deserialized as a Map and the JSON array as an ArrayList.