Java:如何动态覆盖类的方法(类最终不在类路径中)?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3431770/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Java: How do I override a method of a class dynamically (class is eventually NOT in classpath)?
提问by java.is.for.desktop
How do I call a method of a class dynamically + conditionally?
(Class is eventually notin classpath)
如何动态+有条件地调用类的方法?
(类最终不在类路径中)
Let's say, I need the class NimbusLookAndFeel, but on some systems it's not available (i.e. OpenJDK-6).
比方说,我需要 class NimbusLookAndFeel,但在某些系统上它不可用(即OpenJDK-6)。
So I must be able to:
所以我必须能够:
- Get to know it that class is available(at runtime),
- If it's not the case, skipthe 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 NoClassDefFoundErrorusing try-catch. It fails. I don't know, if it's OpenJDK's fault. I get InvocationTargetException, caused by NoClassDefFoundError. Funny, that I can'tcatch InvocationTargetException: It's thrown anyway.
编辑:
现在我按照建议编辑了我的代码,以NoClassDefFoundError使用 try-catch进行拦截。它失败。我不知道,如果是 OpenJDK 的错。我得到InvocationTargetException,造成的NoClassDefFoundError。有趣的是,我无法抓住InvocationTargetException:无论如何它都会被抛出。
EDIT2::
Cause found: I was wrapping SwingUtilities.invokeAndWait(...)around the tested method, and that very invokeAndWaitcall throws NoClassDefFoundErrorwhen loading Nimbus fails.
EDIT2: :
原因发现:我包裹SwingUtilities.invokeAndWait(...)周围的测试方法,那很invokeAndWait调用抛出NoClassDefFoundError时加载雨云失败。
EDIT3::
Can anyone please clarify whereNoClassDefFoundErrorcan occur at all? Because it seems that it's always the calling method, not the actual method which uses the non-existing class.
EDIT3: :
任何人都可以请澄清哪里NoClassDefFoundError都可以发生?因为它似乎始终是调用方法,而不是使用不存在的类的实际方法。
采纳答案by whiskeysierra
Get to know it that class is available (at runtime)
Put the usage in a try block ...
了解该类可用(在运行时)
将用法放在 try 块中...
If it's not the case, skip the whole thing
... and leave the catch block empty (code smell?!).
如果不是这种情况,请跳过整个过程
……并将 catch 块留空(代码异味?!)。
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:
所以你的代码可能看起来像:
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);
}
}
回答by Siu Ching Pong -Asuka Kenji-
The follow code should solve your problem. The Mainclass simulates your main class. Class Asimulates the base class you want to extend (and you have no control of). Class Bis the derived class of class A. Interface Csimulates "function pointer" functionality that Java does not have. Let's see the code first...
以下代码应该可以解决您的问题。该Main级模拟主类。ClassA模拟您想要扩展的基类(并且您无法控制)。ClassB是 class 的派生类A。接口C模拟了 Java 没有的“函数指针”功能。我们先看代码...
The following is class A, the class you want to extend, but have no control of:
以下是 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);
}
}
The following is class B, the dummy derived class. Notice that, since it extends A, it must import packageA.Aand class Amust be available at the compile time of class B. A constructor with parameter C is essential, but implementing interface Cis optional. If Bimplements C, you gain the convenience to call the method(s) on an instance of Bdirectly (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):
以下是 class B,虚拟派生类。请注意,由于它扩展了A,它必须导入packageA.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);
}
}
The following is the tricky interface C. Just put all the methods you want to override into this interface:
以下是棘手的界面C。只需将您要覆盖的所有方法放入此接口:
/* 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 Mainclass, only packageC.Cis imported, and there is no reference to packageA.Aor packageB.B. If there is any, the class loader will throw an exception on platforms that don't have packageA.Awhen it tries to load one of them.
为什么要编译和运行?
可以看到,在Main类中,只有packageC.C被导入,并没有对packageA.Aor 的引用packageB.B。如果有,类加载器将packageA.A在尝试加载其中一个平台时在没有的平台上抛出异常。
How does it work?
In the first Class.forName(), it checks whether class Ais available on the platform. If it is, ask the class loader to load class B, and store the resulting Classobject in classB. Otherwise, ClassNotFoundExceptionis thrown by Class.forName(), and the program goes without class A.
它是如何工作的?
首先Class.forName(),它检查类A是否在平台上可用。如果是,请类加载器加载 class B,并将结果Class对象存储在classB. 否则,ClassNotFoundException由 抛出Class.forName(),程序将没有类A。
Then, if classBis not null, get the constructor of class Bthat accepts a single Cobject as parameter. Store the Constructorobject in constructorB.
然后,如果classB不为空,则获取B接受单个C对象作为参数的类的构造函数。将Constructor对象存储在constructorB.
Then, if constructorBis not null, invoke constructorB.newInstance()to create a Bobject. Since there is a Cobject as parameter, you can create an anonymous class that implements interface Cand pass the instance as the parameter value. This is just like what you do when you create an anonymous MouseListener.
然后,如果constructorB不为空,则调用constructorB.newInstance()创建一个B对象。由于有一个C对象作为参数,您可以创建一个实现接口的匿名类C并将实例作为参数值传递。这就像您在创建匿名MouseListener.
(In fact, you don't have to separate the above tryblocks. It is done so to make it clear what I am doing.)
(实际上,您不必将上述try块分开。这样做是为了清楚我在做什么。)
If you made Bimplements C, you can cast the Bobject as a Creference at this time, and then you can call the overridden methods directly (without reflection).
如果制作了Bimplements C,此时可以将B对象转换为C引用,然后就可以直接调用被覆盖的方法(无需反射)。
What if class Adoes 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).
如果类A没有“无参数构造函数”怎么办?
只需将所需的参数添加到 class 中B,例如public B(int extraParam, C c),然后调用super(extraParam)而不是super()。创建 时constructorB,还要添加额外的参数,例如classB.getConstructor(Integer.TYPE, C.class).
What happens to String sand String t?tis used by the anonymous class directly. When objectB.doSomething("World");is called, "World"is the ssupplied to class B. Since supercan't be used in the anonymous class (for obvious reasons), all the code that use superare placed in class B.
Strings和 String会发生什么t?t由匿名类直接使用。当objectB.doSomething("World");被调用时,"World"是s提供给 class 的B。由于super不能在匿名类中使用(原因很明显),所以所有使用的代码super都放在 class 中B。
What if I want to refer to supermultiple times?
Just write a template in B.doSomething()like this:
如果我想super多次引用怎么办?
只需B.doSomething()像这样写一个模板:
@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 Cto include doSomethingAfter1()and doSomethingAfter2().
当然,您必须修改接口C以包含doSomethingAfter1()和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.Bis not compiled (since Main.javadoes not have any reference to it). In the second run, the class is explicitly compiled, and thus you get the result you expected.
在第一次运行时,类packageB.B没有被编译(因为Main.java没有任何对它的引用)。在第二次运行中,该类被显式编译,因此您会得到预期的结果。
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 外观和感觉的正确方法的链接:
回答by Kirk Woll
Use BCEL to generate your dynamic subclass on the fly.
使用 BCEL 即时生成您的动态子类。
回答by meriton
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?
emmm,你能不能把你想扩展的类放到编译时的类路径中,像往常一样写你的子类,在运行时,显式触发加载子类,并处理链接器抛出的任何表明超类是的异常失踪?
回答by Pablo Santa Cruz
You can use Classclass to do that.
您可以使用Class类来做到这一点。
I.E.:
IE:
Class c = Class.forName("your.package.YourClass");
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 in cto create objects of your.package.YourClassclass. If you need to call a specific constructor, you can use getConstructorsmethod to get one and use it to create a new instance.
如果在当前类路径中找不到,上面的句子将抛出 ClassNotFoundException。如果没有抛出异常,那么你可以使用newInstance()方法 inc创建your.package.YourClass类的对象。如果您需要调用特定的构造函数,您可以使用getConstructorsmethod 获取一个并使用它来创建一个新实例。

