[英]How do I override a class method in Java and add a “throws” declaration to it?
[英]Java: How do I override a method of a class dynamically (class is eventually NOT in classpath)?
如何動態地+有條件地調用類的方法?
(類最終不在類路徑中)
假設我需要類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
引起的InvocationTargetException
。 有趣的是,我無法捕獲InvocationTargetException
:無論如何,它被拋出。
EDIT2 ::
找到原因:我將SwingUtilities.invokeAndWait(...)
包裝在經過測試的方法周圍,並且在加載Nimbus失敗時,非常invokeAndWait
調用引發NoClassDefFoundError
。
EDIT3 ::
任何人都可以澄清在哪里可能發生NoClassDefFoundError
嗎? 因為似乎總是調用方法,而不是使用不存在的類的實際方法。
了解類是否可用(在運行時)
將用法放在try塊中...
如果不是這種情況,請跳過整個過程
...,然后將捕獲塊留空(代碼氣味?!)。
我如何設法覆蓋動態加載的類的方法
只要做到這一點,並確保滿足編譯時依賴性。 您正在把事情混在一起。 覆蓋發生在編譯時,而類加載是運行時的事情。
為了完整性,需要時,運行時環境會動態加載您編寫的每個類。
因此您的代碼可能如下所示:
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);
}
}
使用BCEL即時生成動態子類。
以下代碼應該可以解決您的問題。 Main
類模擬您的主類。 類A
模擬您要擴展的基類(您無法控制)。 B
類是A
類的派生類。 接口C
模擬Java沒有的“函數指針”功能。 我們先來看代碼...
以下是類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);
}
}
以下是類B
(偽派生類)。 請注意,由於它擴展了A
,因此必須導入packageA.A
並且在類B
的編譯時類A
必須可用。 具有參數C的構造函數是必不可少的,但實現接口C
是可選的。 如果B
實現C
,則可以方便地直接在B
的實例上調用方法(無反射)。 在B.doSomething()
,調用super.doSomething()
是可選的,並取決於您是否c.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.A
或packageB.B
引用。 如果有的話,類加載器將在嘗試不加載packageA.A
平台上拋出異常。
它是如何工作的?
在第一個Class.forName()
,它檢查類A
在平台上是否可用。 如果是,請讓類加載器加載類B
,並將生成的Class
對象存儲在classB
。 否則, Class.forName()
會引發ClassNotFoundException
,並且程序將不包含A
類。
然后,如果classB
不為null,則獲取接受單個C
對象作為參數的B
類的構造函數。 存儲Constructor
的對象constructorB
。
然后,如果constructorB
constructorB.newInstance()
B
不為null,則調用constructorB.newInstance()
B.newInstance 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)
。
String s
和String t
怎樣?
t
由匿名類直接使用。 當objectB.doSomething("World");
被稱為, "World"
是s
供應給類B
。 由於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 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
在第一次運行中,不編譯packageB.B
類(因為Main.java
沒有對其的任何引用)。 在第二次運行中,該類被顯式編譯,因此您將獲得預期的結果。
為了幫助您使我的解決方案適應您的問題,以下是設置Nimbus外觀的正確方法的鏈接:
您可以使用Class類來做到這一點。
IE瀏覽器:
Class c = Class.forName("your.package.YourClass");
如果在當前classpath上找不到,則以上語句將引發ClassNotFoundException。 如果未引發異常,則可以在c
使用newInstance()
方法創建.package.YourClass類的對象。 如果需要調用特定的構造函數,則可以使用getConstructors
方法獲取一個並使用它創建一個新實例。
恩,您不能將要擴展的類放入編譯時類路徑中,照常編寫子類,然后在運行時顯式觸發加載子類,並處理鏈接器拋出的任何異常,這些異常表明超類是失蹤?
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.