Foiled by Erasure!
Consider this EventSubscriber interface:
public interface EventSubscriber<T> {
void onEvent(T event);
}
To subscribe to a single event type, you write something like this:
public class MyBean implements EventSubscriber<String> {
public void onEvent(String event) {
// handle String events
}
}
I then decided I wanted to subscribe to multiple events. I foolishly tried this:
public class MyBean implements EventSubscriber<String>,
EventSubscriber<StatusEvent> {
public void onEvent(String event) {
// handle String events
}
public void onEvent(StatusEvent event) {
// handle status events
}
}
Oops. Won’t compile.
IDEA says:
Duplicate class: ‘com.stuffthathappens.classbus.EventSubscriber’
javac says:
Error:(33,8): com.stuffthathappens.classbus.EventSubscriber cannot be inherited with different arguments: <com.stuffthathappens.demo.StatusEvent> and <java.lang.String>
Section 8.1.5 of the Java Language Specification says this:
A compile-time error occurs if the same interface is mentioned as a direct superinterface two or more times in a single implements clause names.
Hmm…that sounds really close, but not quite. The end of section 8.1.5 contains the answer:
A class may not at the same time be a subtype of two interface types which are different invocations of the same generic interface (§9.1.2), or an invocation of a generic interface and a raw type naming that same generic interface.
Discussion
Here is an example of an illegal multiple inheritance of an interface:
class B implements I<Integer>
class C extends B implements I<String>This requirement was introduced in order to support translation by type erasure (§4.6).
Foiled by erasure!
Is erasing erasure still on the table for Java 7? I’m no compiler expert…would reification allow me to do what I tried to do above?
I think this might be more indirectly related to erasure. I mean, method calls are bound at compile time. You have all the type information you need for this to work. Actually, you have all the information you’d need at runtime, too. I’ll bet the real problem is that calls to the methods with raw types would be ambiguous.
The naive way for an improved compiler to handle this would be:
public void onEvent(Object event) {
. if (event instanceof String) {
. . onEvent((String) event);
. } else if (event instanceof StatusEvent ) {
. . onEvent((StatusEvent) event);
. } else {
. . throw new ClassCastException(”Invalid use of generics”);
. }
}
It would appear at first glance that this could be done without erasing erasure. The question is whether it would perform well enough for you?
Erasure itself isn’t really a bad thing; Haskell erases types but doesn’t have this kind of problem. You can still do type-based dispatch in Haskell, but at compile-time (typeclasses). Scala appears to exhibit the same problem as Java here, but has something equivalent to typeclasses – implicits, so you can still do most things you’d like to be able to.
I suppose you could split MyBean up into the bean plus a couple of EventSubscribers.