Nitiraj
Nitiraj

Reputation: 644

java generics compiles in eclipse and not from commandline

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

Answers (2)

cornz
cornz

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

meriton
meriton

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 Events. However, verifying the EventHandler's event type is in general not possible due to type erasure.

Upvotes: 0

Related Questions