以下代码应该可以解决您的问题。 Main
类模拟了您的主类。类 A
模拟了您想要扩展的基类(您无法控制)。类 B
是类 A
的派生类。接口 C
模拟了 Java 没有的“函数指针”功能。让我们先看一下代码...
以下是类 A
,这是您想要扩展但无法控制的类:
package packageA;
public class A {
public A() {
}
public void doSomething(String s) {
System.out.println("This is from packageA.A: " + s);
}
}
以下是类B,虚拟派生类。请注意,由于它扩展了A,因此必须导入packageA.A和类A必须在类B的编译时可用。具有参数C的构造函数是必不可少的,但实现接口C是可选的。如果B实现了C,则可以方便地直接调用B实例上的方法(无需反射)。在B.doSomething()中,调用super.doSomething()是可选的,取决于您是否需要这样做,但调用c.doSomething()是必要的(下面解释):
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);
}
}
以下是有点棘手的接口 C
。只需将您想要覆盖的所有方法放入此接口:
package packageC;
public interface C {
public void doSomething(String s);
}
以下是主类:
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");
}
}
}
为什么它可以编译和运行?
您可以看到,在
Main
类中,只导入了
packageC.C
,没有引用
packageA.A
或
packageB.B
。如果有的话,在尝试加载其中一个时,类加载器将在没有
packageA.A
的平台上抛出异常。
它是如何工作的?
在第一个Class.forName()
中,它检查平台上是否有类A
。如果有,则请求类加载器加载类B
,并将结果存储在classB
中。否则,Class.forName()
会抛出ClassNotFoundException
,程序将不使用类A
。
然后,如果classB
不为空,请获取接受单个C
对象作为参数的类B
的构造函数。将Constructor
对象存储在constructorB
中。
然后,如果constructorB
不为null,则调用constructorB.newInstance()
来创建一个B
对象。由于有一个C
对象作为参数,因此可以创建一个实现接口C
的匿名类,并将该实例作为参数值传递。这就像创建匿名MouseListener
时所做的一样。
(实际上,您不必将上述try
块分开。这样做是为了清楚地说明我正在做什么。)
如果您使B
实现C
,则此时可以将B
对象强制转换为C
引用,然后可以直接调用重写的方法(无需反射)。
如果类 A
没有“无参构造函数”怎么办?
只需将所需参数添加到类 B
中,例如 public B(int extraParam, C c)
,并调用 super(extraParam)
而不是 super()
。在创建 constructorB
时,还要添加额外的参数,例如 classB.getConstructor(Integer.TYPE, C.class)
。
字符串 s
和字符串 t
会发生什么?
t
直接由匿名类使用。当调用 objectB.doSomething("World");
时,"World"
是提供给类 B
的 s
。由于匿名类中不能使用 super
(显而易见的原因),所有使用 super
的代码都放在类 B
中。
如果我想多次引用 super
怎么办?
只需在 B.doSomething()
中编写一个模板,如下所示:
@Override
public void doSomething(String s) {
super.doSomething1(s);
c.doSomethingAfter1(s);
super.doSomething2(s);
c.doSomethingAfter2(s);
}
当然,你需要修改接口C
以包括doSomethingAfter1()
和doSomethingAfter2()
。
如何编译和运行代码?
$ mkdir classes
$
$
$
$ javac -cp src -d classes src/Main.java
$ java -cp classes Main
packageA.A未找到。继续执行!
$
$
$
$ javac -cp src -d classes src/packageB/B.java
$ java -cp classes Main
这是来自packageA.A的内容:World
这是来自匿名内部类的内容:Hello
在第一次运行时,类packageB.B
没有被编译(因为Main.java
没有任何引用它)。在第二次运行中,该类被显式编译,因此您会得到预期的结果。
为了帮助您将我的解决方案适应于您的问题,这里有一个链接,指导正确设置Nimbus外观:
Nimbus外观
NoClassDefFoundError
在加载编译时类路径中存在但在运行时类路径中不存在的类时发生。ClassNotFoundException
在加载运行时类路径中不存在但不需要出现在编译时类路径中的类时发生。 - BalusC@BalusC
: 我在 EDIT3 中的问题与 EDIT2 有关:是否有地方指定NoClassDefFoundError
发生在类的构造期间,该类试图调用另一个不存在的类,或者仅在调用调用不存在的类的 方法 时才发生,... 更一般地说:何时指定某个类被加载? - java.is.for.desktop