0

I have a java library named testlib.jar and it has an abstract class as below:

com.test;
public abstract class AbstractClass {
    public abstract void foo();
}

and a class named MyClass as below:

com.test;
public class MyClass extends AbstractClass{
    @Override
    public void foo() {
        System.out.println("foo is called in MyClass");
    }
}

that's it. In my application I have an abstract class as below:

com.test;
public abstract class AbstractClass {
    public abstract void foo();
}

So it's the same as the abstract class in testlib.jar. Here I loaded the testlib.jar file and instantiated an object of MyClass and casted it to the abstract class that is known to my application (the second one):

URLClassLoader urlClassLoader = new URLClassLoader(
            new URL[]{new URL("file:\\path\\to\\testlib.jar")});
Class<AbstractClass> MyClass = (Class<AbstractClass>)urlClassLoader.loadClass("com.test.MyClass");
Constructor<AbstractClass> c = MyClass.getConstructor();
AbstractClass myObject = c.newInstance();
myObject.foo();

Output:

foo is called in MyClass

Note that I'm casting MyClass to a class that is not really its parent.

Know here I've two questions:

1- Does this approach work in all JVMs?

2- How does JVM handle this cast operation internally?

amrezzd
  • 1,787
  • 15
  • 38
  • Have a look at [which-java-class-file-will-be-called-if-same-class-is-packed-in-two-jar-files](https://stackoverflow.com/questions/9757649/which-java-class-file-will-be-called-if-same-class-is-packed-in-two-jar-files) – zhh Aug 05 '18 at 10:03
  • Even if it works, that's a mess. You should really avoid to rely on prioritising one jar over another to load a specific definition of a class which FQN is shared with other classes on the classpath. I would question why you have two different version of the same FQN and why you have to choose which one is loaded. – Alexandre Dupriez Aug 05 '18 at 10:11
  • @zhh great hint, thank you. But this is a little different cuz i'm using dynamic loading and reflection. – amrezzd Aug 05 '18 at 10:15

2 Answers2

1

I think that what you are trying to do is counter intuitive and error prone.
You don't have to use such an approach. Instead, extract the API that you want to share in a library and share it in the both sides : provider and clients.

Besides your example is flawed because you use the URLClassLoader constructor that rely on the parent class loader :

URLClassLoader urlClassLoader = new URLClassLoader(
            new URL[]{new URL("file:\\path\\to\\testlib.jar")});

As stated by the documentation :

Constructs a new URLClassLoader for the specified URLs using the default delegation parent ClassLoader. The URLs will be searched in the order specified for classes and resources after first searching in the parent class loader.

So it means that here :

Class<AbstractClass> MyClass = (Class<AbstractClass>)urlClassLoader.loadClass("com.test.MyClass");

you load the class defined in your current classpath, not in the JAR.
So this assignment can only work as the AbstractClass declared type refers also which one in the current classpath, not in the JAR :

AbstractClass myObject = c.newInstance();

Don't use the default parent classloader and you would see probably the throw of java.lang.ClassCastException :

URLClassLoader urlClassLoader = new URLClassLoader(
            new URL[]{new URL("file:\\path\\to\\testlib.jar")}, null);
davidxxx
  • 125,838
  • 23
  • 214
  • 215
  • What do you mean by `extract the API that you want to share in a library and share it in the both sides`. – amrezzd Aug 05 '18 at 11:48
  • AbstractClass should be in a JAR in the classpath of the client. Suppose I am a provider for this class, I package the API you need in a JAR, I provide this JAR to you to be added as dependency in your classpath rather than you copy the content class in your source code. – davidxxx Aug 05 '18 at 12:39
  • what about when the provider class need to access the client class too? – amrezzd Aug 12 '18 at 04:28
0

Just tested this approach on Android 7.1.1 using DexClassLoader and it worked, but it failed when testing on Android 4.2.2.

amrezzd
  • 1,787
  • 15
  • 38