Java:如何动态重写类的方法(类最终不在类路径中)?
如何动态+有条件地调用类的方法?
(类最终不在在类路径中)
比方说,我需要类 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;
}
});
}
编辑:
现在,我按照建议编辑了代码,使用 try-catch 拦截 NoClassDefFoundError
。它失败了。我不知道,这是不是 OpenJDK 的错。我收到由 NoClassDefFoundError
引起的 InitationTargetException
。有趣的是,我无法捕获InitationTargetException
:无论如何它都会被抛出。
编辑2::
发现原因:我将 SwingUtilities.invokeAndWait(...)
包裹在测试方法周围,并且在加载 Nimbus 时,invokeAndWait
调用抛出 NoClassDefFoundError
失败。
编辑3::
任何人都可以澄清 哪里 NoClassDefFoundError
可能发生吗?因为它似乎总是调用方法,而不是使用不存在的类的实际方法。
How do I call a method of a class dynamically + conditionally?
(Class is eventually not in classpath)
Let's say, I need the class NimbusLookAndFeel
, but on some systems it's not available (i.e. OpenJDK-6
).
So I must be able to:
- Get to know it that class is available (at runtime),
- If it's not the case, skip the whole thing.
- How do I manage to override a method of a dynamically-loaded class
(thus creating an anonymous inner sub-class of it)?
Code example
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;
}
});
}
EDIT:
Now I edited my code, as it was suggested, to intercept NoClassDefFoundError
using try-catch. It fails. I don't know, if it's OpenJDK's fault. I get InvocationTargetException
, caused by NoClassDefFoundError
. Funny, that I can't catch InvocationTargetException
: It's thrown anyway.
EDIT2::
Cause found: I was wrapping SwingUtilities.invokeAndWait(...)
around the tested method, and that very invokeAndWait
call throws NoClassDefFoundError
when loading Nimbus fails.
EDIT3::
Can anyone please clarify where NoClassDefFoundError
can occur at all? Because it seems that it's always the calling method, not the actual method which uses the non-existing class.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
了解类是否可用(在运行时)
将用法放在 try 块中...
如果不是这样,请跳过整个事情
...并将 catch 块留空(代码异味?!)。
如何设法重写动态加载类的方法
只需执行此操作并确保满足编译时依赖性即可。你在这里把事情搞混了。覆盖发生在编译时,而类加载是运行时的事情。
为了完整起见,您编写的每个类都会在需要时由运行时环境动态加载。
所以你的代码可能看起来像:
Get to know it that class is available (at runtime)
Put the usage in a try block ...
If it's not the case, skip the whole thing
... and leave the catch block empty (code smell?!).
How do I manage to override a method of a dynamically-loaded class
Just do it and make sure the compile-time dependency is satisfied. You are mixing things up here. Overriding takes place at compile time while class loading is a runtime thing.
For completeness, every class you write is dynamically loaded by the runtime environment when it is required.
So your code may look like:
使用 BCEL 动态生成动态子类。
http://jakarta.apache.org/bcel/manual.html
Use BCEL to generate your dynamic subclass on the fly.
http://jakarta.apache.org/bcel/manual.html
以下代码应该可以解决您的问题。
Main
类模拟您的主类。类A
模拟您想要扩展的基类(您无法控制)。类B
是类A
的派生类。接口C
模拟了Java 所没有的“函数指针”功能。让我们先看一下代码...以下是类
A
,您想要扩展但无法控制的类:以下是类
B
,即派生的虚拟类班级。请注意,由于它扩展了A
,因此它必须导入packageA.A
并且类A
必须在类的编译时可用B。带参数 C 的构造函数是必需的,但实现接口 C 是可选的。如果
B
实现C
,您可以方便地直接调用B
实例上的方法(无需反射)。在B.doSomething()
中,调用super.doSomething()
是可选的,取决于您是否想要这样做,但调用c.doSomething()
> 是必不可少的(解释如下):以下是棘手的接口
C
。只需将所有要重写的方法放入该接口即可:以下是主类:
为什么会编译并运行?
可以看到,在
Main
类中,只导入了packageC.C
,并没有引用packageA.A
或者packageB.B
。如果有的话,类加载器在尝试加载其中一个时,会在没有packageA.A
的平台上抛出异常。它是如何工作的?
在第一个
Class.forName()
中,它检查类A
在平台上是否可用。如果是,则要求类加载器加载类B
,并将生成的Class
对象存储在classB
中。否则,Class.forName()
会引发ClassNotFoundException
,并且程序将没有类A
。然后,如果
classB
不为 null,则获取类B
的构造函数,该构造函数接受单个C
对象作为参数。将Constructor
对象存储在constructorB
中。然后,如果
constructorB
不为 null,则调用constructorB.newInstance()
创建一个B
对象。由于有一个C
对象作为参数,因此您可以创建一个实现接口C
的匿名类,并将实例作为参数值传递。这就像您创建匿名MouseListener
时所做的那样。(事实上,你不必将上面的
try
块分开。这样做是为了清楚我在做什么。)如果你让
B
实现 < code>C,此时可以将B
对象强制转换为C
引用,然后就可以直接调用重写的方法(无需反射)。如果类
A
没有“无参数构造函数”怎么办?只需将所需的参数添加到类
B
中,例如public B(int extraParam, C c)
,并调用super(extraParam)
而不是 <代码>超级()。创建constructorB
时,还要添加额外的参数,例如classB.getConstructor(Integer.TYPE, C.class)
。字符串
s
和字符串t
会发生什么?t
由匿名类直接使用。当调用objectB.doSomething("World");
时,"World"
是提供给类B
的s
>。由于super
不能在匿名类中使用(出于明显的原因),因此所有使用super
的代码都放在类B
中。如果我想多次引用
super
该怎么办?只需在
B.doSomething()
中编写一个模板,如下所示:当然,您必须修改接口
C
以包含doSomethingAfter1()
和 <代码>doSomethingAfter2()。如何编译和运行代码?
在第一次运行时,类
packageB.B
未编译(因为Main.java
没有任何参考)。在第二次运行中,该类被显式编译,因此您得到了预期的结果。为了帮助您使我的解决方案适合您的问题,这里有一个设置 Nimbus 外观和感觉的正确方法的链接:
Nimbus 外观
The follow code should solve your problem. The
Main
class simulates your main class. ClassA
simulates the base class you want to extend (and you have no control of). ClassB
is the derived class of classA
. InterfaceC
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:The following is class
B
, the dummy derived class. Notice that, since it extendsA
, it must importpackageA.A
and classA
must be available at the compile time of classB
. A constructor with parameter C is essential, but implementing interfaceC
is optional. IfB
implementsC
, you gain the convenience to call the method(s) on an instance ofB
directly (without reflection). InB.doSomething()
, callingsuper.doSomething()
is optional and depends on whether you want so, but callingc.doSomething()
is essential (explained below):The following is the tricky interface
C
. Just put all the methods you want to override into this interface:The following is the main class:
Why does it compile and run?
You can see that in the
Main
class, onlypackageC.C
is imported, and there is no reference topackageA.A
orpackageB.B
. If there is any, the class loader will throw an exception on platforms that don't havepackageA.A
when it tries to load one of them.How does it work?
In the first
Class.forName()
, it checks whether classA
is available on the platform. If it is, ask the class loader to load classB
, and store the resultingClass
object inclassB
. Otherwise,ClassNotFoundException
is thrown byClass.forName()
, and the program goes without classA
.Then, if
classB
is not null, get the constructor of classB
that accepts a singleC
object as parameter. Store theConstructor
object inconstructorB
.Then, if
constructorB
is not null, invokeconstructorB.newInstance()
to create aB
object. Since there is aC
object as parameter, you can create an anonymous class that implements interfaceC
and pass the instance as the parameter value. This is just like what you do when you create an anonymousMouseListener
.(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
implementsC
, you can cast theB
object as aC
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
, likepublic B(int extraParam, C c)
, and callsuper(extraParam)
instead ofsuper()
. When creating theconstructorB
, also add the extra parameter, likeclassB.getConstructor(Integer.TYPE, C.class)
.What happens to String
s
and Stringt
?t
is used by the anonymous class directly. WhenobjectB.doSomething("World");
is called,"World"
is thes
supplied to classB
. Sincesuper
can't be used in the anonymous class (for obvious reasons), all the code that usesuper
are placed in classB
.What if I want to refer to
super
multiple times?Just write a template in
B.doSomething()
like this:Of course, you have to modify interface
C
to includedoSomethingAfter1()
anddoSomethingAfter2()
.How to compile and run the code?
In the first run, the class
packageB.B
is not compiled (sinceMain.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
您可以使用 Class 类来这样做。
IE:
如果在当前类路径中找不到,上面的句子将抛出 ClassNotFoundException。如果没有抛出异常,那么您可以使用
c
中的newInstance()
方法来创建your.package.YourClass类的对象。如果需要调用特定的构造函数,可以使用 getConstructors 方法获取一个构造函数并用它来创建一个新实例。You can use Class class to do that.
I.E.:
The sentence above will throw a ClassNotFoundException if not found on current classpath. If the exception is not thrown, then you can use
newInstance()
method inc
to create objects of your.package.YourClass class. If you need to call a specific constructor, you can usegetConstructors
method to get one and use it to create a new instance.呃,难道你不能将你想要扩展的类放入编译时类路径中,像往常一样编写你的子类,并在运行时显式触发加载子类,并处理链接器抛出的任何指示超类是的异常吗?丢失的?
Erm, can't you put the class you want to extend into the compile time class path, write your subclass as usual, and at runtime, explicitly trigger loading the subclass, and handle any exception thrown by the linker that indicates that the superclass is missing?