10

如何动态+有条件地调用类的方法?
(类最终不在类路径中)

比方说,我需要 class NimbusLookAndFeel,但在某些系统上它不可用(即OpenJDK-6)。

所以我必须能够:

  • 了解该类可用(在运行时),
  • 如果不是这种情况,请跳过整个过程。
  • 我如何设法覆盖动态加载类的方法
    (从而创建它的匿名内部子类)?

代码示例

public static void setNimbusUI(final IMethod<UIDefaults> method)
    throws UnsupportedLookAndFeelException {

  // NimbusLookAndFeel may be now available
  UIManager.setLookAndFeel(new NimbusLookAndFeel() {

    @Override
    public UIDefaults getDefaults() {
      UIDefaults ret = super.getDefaults();
      method.perform(ret);
      return ret;
    }

  });
}

编辑
现在我按照建议编辑了我的代码,以NoClassDefFoundError使用 try-catch 进行拦截。它失败。我不知道,这是否是 OpenJDK 的错。我明白InvocationTargetException了,造成的NoClassDefFoundError。有趣的是,我无法捕捉到InvocationTargetException:无论如何它都被抛出了。

EDIT2: :
发现原因:我正在环绕SwingUtilities.invokeAndWait(...)测试的方法,并且在加载 Nimbus 失败时invokeAndWait会抛出该调用。NoClassDefFoundError

EDIT3: :
任何人都可以澄清一下可能发生的地方吗? NoClassDefFoundError因为它似乎总是调用方法,而不是使用不存在的类的实际方法。

4

5 回答 5

4

了解该类是否可用(在运行时)
将用法放在 try 块中...

如果不是这种情况,请跳过整个事情
......并将 catch 块留空(代码味道?!)。

我如何设法覆盖动态加载类的方法
只需执行此操作并确保满足编译时依赖性。你在这里把事情搞混了。覆盖发生在编译时,而类加载是运行时的事情。

为了完整起见,您编写的每个类都在需要时由运行时环境动态加载。

所以你的代码可能看起来像:

public static void setNimbusUI(final IMethod<UIDefaults> method)
    throws UnsupportedLookAndFeelException {

    try {
        // NimbusLookAndFeel may be now available
        UIManager.setLookAndFeel(new NimbusLookAndFeel() {

            @Override
            public UIDefaults getDefaults() {
                final UIDefaults defaults = super.getDefaults();
                method.perform(defaults);
                return defaults;
            }

        });
   } catch (NoClassDefFoundError e) {
       throw new UnsupportedLookAndFeelException(e);
   }
}
于 2010-08-07T21:24:26.320 回答
1

使用 BCEL 动态生成动态子类。

http://jakarta.apache.org/bcel/manual.html

于 2010-08-07T20:04:56.703 回答
1

以下代码应该可以解决您的问题。该Main课程模拟您的主要课程。类A模拟您要扩展的基类(并且您无法控制)。ClassB是 class 的派生类A。接口C模拟 Java 没有的“函数指针”功能。我们先来看代码...

以下是 class A,您要扩展但无法控制的类:


/* 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);
    }
}

下面是 class B,虚拟派生类。请注意,由于它 extends A,它必须 importpackageA.A并且 classA必须在 class 的编译时可用B。带有参数 C 的构造函数是必不可少的,但实现接口C是可选的。如果Bimplements C,您可以方便地直接调用实例上的方法B(无需反射)。在B.doSomething()中,调用super.doSomething()是可选的,取决于你是否愿意,但调用c.doSomething()是必不可少的(解释如下):


/* 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);
    }
}

以下是棘手的界面C。只需将您要覆盖的所有方法放入此接口:


/* src/packageC/C.java */

package packageC;

public interface C {
    public void doSomething(String s);
}

以下是主要课程:


/* 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");
        }
    }
}

为什么它会编译和运行?
可以看到,在Main类中,只有packageC.C被导入,没有对packageA.Aor的引用packageB.B。如果有,类加载器将在packageA.A尝试加载其中一个时在没有的平台上抛出异常。

它是如何工作的?
首先Class.forName(),它检查A平台上是否有可用的类。如果是,请让类加载器加载 class B,并将生成的Class对象存储在classB. 否则,ClassNotFoundException被 抛出Class.forName(),并且程序没有类A

然后,如果classB不为 null,则获取B接受单个C对象作为参数的类的构造函数。将Constructor对象存储在constructorB.

然后,如果constructorB不为 null,则调用constructorB.newInstance()以创建B对象。由于有一个C对象作为参数,您可以创建一个实现接口的匿名类C并将实例作为参数值传递。这就像您在创建匿名 时所做的一样MouseListener

(事实上​​,你不必将上面的try块分开。这样做是为了清楚我在做什么。)

如果你做了Bimplements C,你可以在这个时候将B对象转换为C引用,然后你可以直接调用被覆盖的方法(无需反射)。

如果类A没有“无参数构造函数”怎么办?
只需将所需的参数添加到类B中,例如public B(int extraParam, C c),然后调用super(extraParam)而不是super(). 创建 时constructorB,还要添加额外的参数,例如classB.getConstructor(Integer.TYPE, C.class).

Strings和 String会发生什么t
t由匿名类直接使用。当objectB.doSomething("World");被调用时,"World"s提供给 class B。由于super不能在匿名类中使用(原因很明显),所有使用的代码super都放在 class 中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 类
$
$
$
$ javac -cp src -d 类 src/Main.java
$ java -cp 类主要
未找到 packageA.A。没有它!
$
$
$
$ javac -cp src -d 类 src/packageB/B.java
$ java -cp 类主要
这是来自 packageA.A: World
这是来自匿名内部类:你好

在第一次运行时,该类packageB.B没有被编译(因为Main.java没有对它的任何引用)。在第二次运行中,该类被显式编译,因此您得到了预期的结果。

为了帮助您解决我的问题,这里是设置 Nimbus 外观的正确方法的链接:

Nimbus 外观和感觉

于 2010-08-11T14:56:50.433 回答
0

您可以使用Class类来做到这一点。

IE:

Class c = Class.forName("your.package.YourClass");

如果在当前类路径中找不到,上面的句子将抛出 ClassNotFoundException。如果没有抛出异常,那么您可以使用newInstance()方法 inc创建your.package.YourClass类的对象。如果您需要调用特定的构造函数,您可以使用getConstructors方法获取一个并使用它来创建一个新实例。

于 2010-08-07T19:44:20.210 回答
-1

呃,你不能把你要扩展的类放到编译时类路径中,像往常一样写你的子类,在运行时,显式触发加载子类,并处理链接器抛出的任何表明超类的异常失踪?

于 2010-08-07T21:04:43.743 回答