Reputation: 4675
I have a jar:
/home/cole/lib/a.jar
And in this jar I have the following interface/classes (horrible names for illustration purposes only!):
CreatorInterface.java
Base.java (implements CreatorInterface.java)
AbstractBase.java (extends Base.java)
Implementation.java (extends AbstractBase.java)
In a separate project I have the following code:
final URL[] jars = new URL[] {
new File("/home/cole/lib/a.jar").toURL();
}
final URLClassLoader classLoader = new URLClassLoader(jars, null);
final Class<?> implementation = classLoader.loadClass("Implementation");
final CreatorInterface object = (CreatorInterface)implementation.newInstance();
However when I run the above, I get the following:
java.lang.ClassCastException: Implementation cannot be cast to CreatorInterface
Given Implementation
is ultimately an instance of a class that implements CreatorInterface
, why do I get the ClassCastException
?
Update 1
This isn't a question about using URLClassLoader
, the class is found ok, the problem appears to be in the instantiation. For example, the following code works fine:
final Object object = implementation.newInstance();
Update 2
As @davidxxx answered, I have the interface class twice (once in the jar and once in the project using it). Although the interface was the same, this was the cause of the issue.
However to make it work, I needed to fix my URLClassLoader like this, to avoid a ClassNotFoundException
:
final ClassLoader parent = this.getClass().getClassLoader();
final URLClassLoader classLoader = new URLClassLoader(jars, parent);
Upvotes: 0
Views: 966
Reputation: 131346
This exception :
java.lang.ClassCastException: Implementation cannot be cast to CreatorInterface
makes me think that you have very probably two distinct CreatorInterface
classes : one included in the jar and another other coming from the client program that tries to load it.
Even if the two classes have the same name (qualified names), these are different classes for each classloader as here you use two unassociated classloaders.
You have the current classloader of the program that you run and this other classloader as you specified null
as parent classloader :
final URLClassLoader classLoader = new URLClassLoader(jars, null);
So as you try to assign the object created by reflection to the CreatorInterface
variable, the cast fails because two distinct CreatorInterface
were loaded by each classloader and are used : one coming from the classloader of your client code and another coming from the the instantiated classloader.
Using a single classloader would solve the issue but a best practice would be including the jar in the classpath of the project and to ensure to have a single version of the classes provided in the jar.
To decouple things you should probably split the jar into 2 jars : an API jar that contains only the interface and an implementation jar that depends on the API jar and that contains other classes.
In the client code, add in the classpath only the API jar to be able to assign to a variable with the interface declared type.
About your second point :
This isn't a question about using URLClassLoader, the class is found ok, the problem appears to be in the instantiation. For example, the following code works fine:
final Object object = implementation.newInstance();
In this case you don't refer the interface type.
You indeed assign the Implementation
object to an Object
and not to a CreatorInterface
variable.
The correct/consistent interfaces and subclasses are loaded by the classloader but here you never give a chance to provoke a ClassCastException
as you never assign it to a type of a duplicate class but Object
that is defined a single time.
So the problem previously encountered cannot occur.
About the third point :
However to make it work, I needed to fix my URLClassLoader like this, to avoid a ClassNotFoundException:
final ClassLoader parent = this.getClass().getClassLoader();
final URLClassLoader classLoader = new URLClassLoader(jars, parent);
It works because here you create a classloader associated to the parent classloader.
In fact if you did :
final URLClassLoader classLoader = new URLClassLoader(jars);
It would produce the same result as the URLClassLoader
object created would use by default the delegation to the parent classloader (here the classloader that started your application).
Upvotes: 1