The follow code should solve your problem. The Main
class simulates your main class. Class A
simulates the base class you want to extend (and you have no control of). Class B
is the derived class of class A
. Interface C
simulates "function pointer" functionality that Java does not have. Let's see the code first...
The following is class A
, the class you want to extend, but have no control of:
/* src/packageA/A.java */
package packageA;
public class A {
public A() {
}
public void doSomething(String s) {
System.out.println("This is from packageA.A: " + s);
}
}
The following is class B
, the dummy derived class. Notice that, since it extends A
, it must import packageA.A
and class A
must be available at the compile time of class B
. A constructor with parameter C is essential, but implementing interface C
is optional. If B
implements C
, you gain the convenience to call the method(s) on an instance of B
directly (without reflection). In B.doSomething()
, calling super.doSomething()
is optional and depends on whether you want so, but calling c.doSomething()
is essential (explained below):
/* src/packageB/B.java */
package packageB;
import packageA.A;
import packageC.C;
public class B extends A implements C {
private C c;
public B(C c) {
super();
this.c = c;
}
@Override
public void doSomething(String s) {
super.doSomething(s);
c.doSomething(s);
}
}
The following is the tricky interface C
. Just put all the methods you want to override into this interface:
/* src/packageC/C.java */
package packageC;
public interface C {
public void doSomething(String s);
}
The following is the main class:
/* src/Main.java */
import packageC.C;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Main {
public static void main(String[] args) {
doSomethingWithB("Hello");
}
public static void doSomethingWithB(final String t) {
Class classB = null;
try {
Class classA = Class.forName("packageA.A");
classB = Class.forName("packageB.B");
} catch (ClassNotFoundException e) {
System.out.println("packageA.A not found. Go without it!");
}
Constructor constructorB = null;
if (classB != null) {
try {
constructorB = classB.getConstructor(C.class);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
C objectB = null;
if (constructorB != null) {
try {
objectB = (C) constructorB.newInstance(new C() {
public void doSomething(String s) {
System.out.println("This is from anonymous inner class: " + t);
}
});
} catch (ClassCastException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
if (objectB != null) {
objectB.doSomething("World");
}
}
}
Why does it compile and run?
You can see that in the Main
class, only packageC.C
is imported, and there is no reference to packageA.A
or packageB.B
. If there is any, the class loader will throw an exception on platforms that don't have packageA.A
when it tries to load one of them.
How does it work?
In the first Class.forName()
, it checks whether class A
is available on the platform. If it is, ask the class loader to load class B
, and store the resulting Class
object in classB
. Otherwise, ClassNotFoundException
is thrown by Class.forName()
, and the program goes without class A
.
Then, if classB
is not null, get the constructor of class B
that accepts a single C
object as parameter. Store the Constructor
object in constructorB
.
Then, if constructorB
is not null, invoke constructorB.newInstance()
to create a B
object. Since there is a C
object as parameter, you can create an anonymous class that implements interface C
and pass the instance as the parameter value. This is just like what you do when you create an anonymous MouseListener
.
(In fact, you don't have to separate the above try
blocks. It is done so to make it clear what I am doing.)
If you made B
implements C
, you can cast the B
object as a C
reference at this time, and then you can call the overridden methods directly (without reflection).
What if class A
does not have a "no parameter constructor"?
Just add the required parameters to class B
, like public B(int extraParam, C c)
, and call super(extraParam)
instead of super()
. When creating the constructorB
, also add the extra parameter, like classB.getConstructor(Integer.TYPE, C.class)
.
What happens to String s
and String t
?
t
is used by the anonymous class directly. When objectB.doSomething("World");
is called, "World"
is the s
supplied to class B
. Since super
can't be used in the anonymous class (for obvious reasons), all the code that use super
are placed in class B
.
What if I want to refer to super
multiple times?
Just write a template in B.doSomething()
like this:
@Override
public void doSomething(String s) {
super.doSomething1(s);
c.doSomethingAfter1(s);
super.doSomething2(s);
c.doSomethingAfter2(s);
}
Of course, you have to modify interface C
to include doSomethingAfter1()
and doSomethingAfter2()
.
How to compile and run the code?
$ mkdir classes
$
$
$
$ javac -cp src -d classes src/Main.java
$ java -cp classes Main
packageA.A not found. Go without it!
$
$
$
$ javac -cp src -d classes src/packageB/B.java
$ java -cp classes Main
This is from packageA.A: World
This is from anonymous inner class: Hello
In the first run, the class packageB.B
is not compiled (since Main.java
does not have any reference to it). In the second run, the class is explicitly compiled, and thus you get the result you expected.
To help you fitting my solution to your problem, here is a link to the correct way to set the Nimbus Look and Feel:
Nimbus Look and Feel