Reputation: 2360
I have the following definition:
public interface MessageResponseHandler<T extends MessageBody> {
public void responsed(Message<T> msg);
}
public class DeviceClientHelper {
public MessageResponseHandler<? extends MessageBody> messageHandler;
setHandler(MessageResponseHandler<? extends MessageBody> h){
this.messageHandler = h;
}
public someMethod(Object message){
Message<? extends MessageBody> msg = (Message<? extends MessageBody>) message;
if (this.messageHandler != null) {
this.messageHandler.responsed(msg);
}
}
}
I couldn't figure out why in the someMethod() method, the invocation of
this.messageHandler.responsed(msg);
would give me a wired compiler error in eclipse. something like:
the method responsed(Message<capture#3-of ? extends MessageBody>) in
the type MessageResponseHandler<capture#3-of ? extends MessageBody> is
not applicable for the arguments (Message<capture#4-of ? extends
MessageBody>)
what is the "catpure" means in the error message anyway?
Upvotes: 2
Views: 786
Reputation: 66109
You're saying DeviceClientHelper
has a messageHandler
for some subclass of MessageBody
. And someMethod
has a message also for some subclass of MessageBody
. However, there's nothing requiring them to be the same subclass of MessageBody
, so the call to responsed
is not valid.
To make them use the same subclass, make the DeviceClientHelper
generic on a specific subclass of MessageBody
like so:
interface MessageResponseHandler<T extends MessageBody> {
public void responsed(Message<T> msg);
}
public class DeviceClientHelper<T extends MessageBody> {
public MessageResponseHandler<T> messageHandler;
void setHandler(MessageResponseHandler<T> h){
this.messageHandler = h;
}
public void someMethod(Object message){
Message<T> msg = (Message<T>) message;
if (this.messageHandler != null) {
this.messageHandler.responsed(msg);
}
}
}
However, your MessageResponseHandler
interface probably doesn't need to care about the MessageBody
class. This depends on how it's actually used, but something like this might work better:
interface MessageResponseHandler {
public void responsed(Message<? extends MessageBody> msg);
}
And then you can remove the generic type from the messageHandler
field and keep the original someMethod
implementation.
Upvotes: 8
Reputation: 1350
This is a common pitfall when using generics; despite your messageHandler and msg objects looking to be the same, they really aren't (or rather, they do not necessarily have to be). Consider this:
class M1 extends MessageBody { ... }
class M2 extends MessageBody { ... }
MessageHandler<? extends MessageBody> handler = new MessageHandler<M1>();
/* So far so good */
Message<? extends MessageBody> message = new M2();
/* Still OK */
handler.responded(message);
/* Huh... you don't want to allow this, right? */
If your handler is only able to handle one specific kind of messages, then your DeviceClientHelper should only receive this specific kind of messages too, right? How about something like this?
public class DeviceClientHelper<T extends MessageBody> {
public MessageResponseHandler<T> messageHandler;
setHandler(MessageResponseHandler<T> h){
this.messageHandler = h;
}
public someMethod(Object message){
Message<T> msg = (Message<T>) message;
if (this.messageHandler != null) {
this.messageHandler.responsed(msg);
}
}
}
(this will most probably produce a warning about unchecked cast)
Or better yet:
public class DeviceClientHelper<T extends MessageBody> {
public MessageResponseHandler<T> messageHandler;
setHandler(MessageResponseHandler<T> h){
this.messageHandler = h;
}
public someMethod(T message){
if (this.messageHandler != null) {
this.messageHandler.responsed(message);
}
}
}
If your DeviceClientHelper is supposed to contain handlers handling different types of messages (for example, it initially contains a handler for M1, but then you assign a handler for M2), and still you want it to be validated by generics, I am afraid you are out of luck; this would require the generics to be checked at runtime instead of compile time, and Java does not support it due to generics' type erasure.
Upvotes: 1
Reputation: 30733
This is due to the wildcard (?)
behavior in generics. Long story short, a variable/parameter typed with a wildcard cannot be assigned-to.
In your code, the call this.messageHandler.responsed(msg)
tries to pass msg
into a parameter whose type is ? extends MessageBody
. It doesn't matter that the actual parameter that you're pssing is also wildcard-typed: as long as the actual parameter is wildcard-typed you cannot pass values to it.
Upvotes: 0