Reputation: 644
I know similar problems have been discussed over several below threads but I am still not able to find a solutions to my problem.
Other threads: Compilers behave differently with a null parameter of a generic method
Java generics code compiles in eclipse but not in command line
So I was writing an event mechanism using the generics for events and event-handlers interfaces. I had following code for registering the events which generates error in command line javac but not with eclipse indigo. In the below code the EventRegistry
just captures the class-name of the event and event-handler in string format.
I have created following interfaces
public interface Event{}
public interface EventHandler<T extends Event> {}
Methods in my EventManager Class
public <T extends Event, P extends EventHandler<T>> void registerManagedHandler( EventRegistry er, ClassLoader cl ) throws ClassNotFoundException
{
...
Class<T> eventClass = (Class<T>) cl.loadClass(er.getEventClass());
Class<P> eventHandlerClass = (Class<P>) cl.loadClass(er.getEventHandlerClass());
...
register(eventHandlerClass, eventClass);
}
private <T extends Event, P extends EventHandler<T>> void register( Class<P> handler, Class<T> eventClass )
{
...
}
... in some other class. I have a statement.
EventManager.getInstance().registerManagedHandler(er,al);
Eclipse compiles the code properly but the error generated while compiling from javac on command line
incompatible types; inferred type argument(s) com.mycompany.events.Event,java.lang.Object do not conform to bounds of type variable(s) T,P
[javac] found : <T,P>void
[javac] required: void
[javac] EventManager.getInstance().registerManagedHandler(er,al);
There are similar methods for unregistering/enabling/disabling
the events which generates the same error.
From the above code I tried removing the type constraints ( <T extends Event, P extends EventHandler<T>> )
from registerManagedHandler but then register(...) method call generated error
my modified registeredManagedHandler()..
public void registerManagedHandler( EventRegistry er, ClassLoader cl ) throws ClassNotFoundException
{
...
Class<? extends Event> eventClass = (Class<? extends Event>) cl.loadClass(er.getEventClass());
Class<? extends EventHandler<? extends Event>> eventHandlerClass = (Class<? extends EventHandler<? extends Event>>) cl.loadClass(er.getEventHandlerClass());
...
register(eventHandlerClass, eventClass);
}
New Generated compile time Error in eclipse too.
Bound mismatch: The generic method register(Class<P>, Class<T>) of type EventManager is not applicable for the arguments
(Class<capture#20-of ? extends EventHandler<? extends Event>>, Class<capture#22-of ? extends Event>). The inferred type
capture#20-of ? extends EventHandler<? extends Event> is not a valid substitute for the bounded parameter <P extends
EventHandler<T>>
I do not intend to remove the type checks from register(...) method.
Please inform me if more details of the code are required for understanding.
Please tell me the right way of handling such issues or work-arounds. I am quite new to generics but I read basic guides available before implementing this.
Also I could not find any way to force eclipse to use the sun-javac6 installed on my Ubuntu system instead of its own compiler. Although I know how to change the JRE in eclipse ( Project -> Properties -> Java Build Path -> Libraries )
Thanks in advance.
Update : Thanks guys for your responses. Tell me if I can provide more information. My Eclipse version is Indigo (3.7)
Here's the sscce. If you run the program in eclipse it works fine (compilation and execution). But when you run it with commandline : i.e. javac GenericTester.java then following error comes. I have confirmed this with the compiler tool of the sscce.org.
interface Event {
}
interface EventHandler<T extends Event> {
}
class EventRegistry {
public String eventClass;
public String eventHandlerClass;
}
class EventManager {
public <T extends Event, P extends EventHandler<T>> void registerEvent(
Class<T> eventClass, Class<P> eventHandlerClass) {
}
public <T extends Event, P extends EventHandler<T>> void registerEvent(
EventRegistry er) throws ClassNotFoundException {
Class<T> eventClass = (Class<T>) this.getClass()
.getClassLoader().loadClass(er.eventClass);
Class<P> eventHandlerClass = (Class<P>) this
.getClass().getClassLoader()
.loadClass(er.eventHandlerClass);
registerEvent(eventClass, eventHandlerClass);
}
}
class MyEvent implements Event {
}
class MyEventHandler implements EventHandler<MyEvent> {
}
public class GenericTester {
public static void main(String[] args) {
EventRegistry er = new EventRegistry();
er.eventClass = MyEvent.class.getName();
er.eventHandlerClass = MyEventHandler.class.getName();
try {
new EventManager().registerEvent(er);
System.out.println("It worked.");
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Error from commandline including the command which I run :
nitiraj@pandora:~/mywork/GenericTest/src$ javac GenericTester.java
GenericTester.java:40: incompatible types; inferred type argument(s) Event,java.lang.Object do not conform to bounds of type variable(s) T,P
found : <T,P>void
required: void
new EventManager().registerEvent(er);
^
Note: GenericTester.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
1 error
Some more information about the java I have installed.
nitiraj@pandora:~/mywork/GenericTest/src$ java -version
java version "1.6.0_30"
Java(TM) SE Runtime Environment (build 1.6.0_30-b12)
Java HotSpot(TM) 64-Bit Server VM (build 20.5-b03, mixed mode)
nitiraj@pandora:~/mywork/GenericTest/src$ javac -version
javac 1.6.0_30
Upvotes: 4
Views: 1984
Reputation: 641
This is a bug in java 5 and java 6, fixed in java 7
https://bugs.java.com/bugdatabase/view_bug?bug_id=6369605
Upvotes: 2
Reputation: 70564
Why JDK 1.6 refused to compile that SSCCE is explained at: Compilers behave differently with a null parameter of a generic method
In Java 7, the inference algorithm for types variables that do not appear in parameter types or return values was extended to take the type parameters' bounds into account. Indeed, your SSCCE compiles successfully with javac from JDK 1.7.0_02.
That said, I think your registerEvent
misuses generics: A method's type parameters are specified by the caller (usually by the type inference algorithm, but they can only be specified explictly), but your code is only type correct if the actual type arguments by the caller match those of the EventRegistry, which is not checked by the compiler. Consider:
er.eventHandlerClass = "java.lang.String";
new EventManager().<MyEvent, MyEventHandler>registerEvent(er);
That compiles and runs fine, but will probably cause a ClassCastException when an event is actually fired. That's why I would not recommend to pass classes as Strings.
If you really need them to be Strings in order to get fancy with the ClassLoader, you should at least check that the classes implement the proper types:
public void registerEvent(EventRegistry er) throws ClassNotFoundException {
Class<? extends Event> eventClass = this.getClass()
.getClassLoader().loadClass(er.eventClass)
.asSubclass(Event.class);
Class<? extends EventHandler> eventHandlerClass = this
.getClass().getClassLoader()
.loadClass(er.eventHandlerClass)
.asSubclass(EventHandler.class);
registerEvent(eventClass, eventHandlerClass);
}
With that implementation, trying to subscribe String
as EventHandler
will cause a ClassCastException
.
That code is still not perfect, as a compiler warning alerts us to the fact that I don't check that the EventHandler has the right type parameter, so a caller could still do:
er.eventClass = Event.class.getName();
er.eventHandlerClass = MyEventHandler.class.getName();
new EventManager().registerEvent(er);
even though a MyEventHandler
can not handle with all Event
s. However, verifying the EventHandler
's event type is in general not possible due to type erasure.
Upvotes: 0