10

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.

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

次のコードで問題が解決するはずです。Mainクラスは、メイン クラスをシミュレートします。ClassAは、拡張する基本クラスをシミュレートします (そして、制御できません)。ClassBは class の派生クラスですA。インターフェイスCは、Java にはない「関数ポインタ」機能をシミュレートします。最初にコードを見てみましょう...

以下はA、拡張したいが制御できないクラスである class です。


/* 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、ダミーの派生クラスである class です。を拡張しているため、Aインポートする必要がありpackageA.A、 classAのコンパイル時に class を使用できる必要があることに注意してくださいB。パラメーター C を持つコンストラクターは必須ですが、インターフェイスの実装Cはオプションです。Bが を実装している場合は、 のインスタンスで直接 (リフレクションなし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");
        }
    }
}

なぜコンパイルして実行するのですか? クラスでは、 のみがインポートされ、またはへの参照がない
ことがわかります。存在する場合、クラスローダーは、それらのいずれかをロードしようとすると、存在しないプラットフォームで例外をスローします。MainpackageC.CpackageA.ApackageB.BpackageA.A

それはどのように機能しますか?
最初のClass.forName()では、クラスAがプラットフォームで使用可能かどうかを確認します。そうである場合は、クラスローダに class をロードするように依頼し、結果のオブジェクトを にB格納します。それ以外の場合は によってスローされ、プログラムは class なしで実行されます。ClassclassBClassNotFoundExceptionClass.forName()A

次に、classBnull でない場合はB、単一のCオブジェクトをパラメーターとして受け入れるクラスのコンストラクターを取得します。Constructorオブジェクトを に格納しますconstructorB

次に、constructorBnull でない場合は、呼び出してオブジェクトconstructorB.newInstance()を作成しBます。パラメーターとしてオブジェクトがあるCため、インターフェイスを実装する匿名クラスを作成しC、インスタンスをパラメーター値として渡すことができます。これは、匿名を作成するときに行うことと同じですMouseListener

(実際には、上記のブロックを分離する必要はありませんtry。これは、私が何をしているのかを明確にするために行っています。)

Bimplementsを作成した場合は、この時点でオブジェクトを参照としてCキャストし、オーバーライドされたメソッドを (リフレクションなしで) 直接呼び出すことができます。BC

クラスAに「パラメーターなしのコンストラクター」がない場合はどうなりますか? 必要なパラメータを class 、 like
に追加し、 の代わりにcallするだけです。を作成するときに、 などの追加のパラメータも追加します。Bpublic B(int extraParam, C c)super(extraParam)super()constructorBclassB.getConstructor(Integer.TYPE, C.class)

Stringsと Stringはどうなりtますか?
t匿名クラスによって直接使用されます。がobjectB.doSomething("World");呼び出されると、"World"sclass に提供されBます。superは (明らかな理由で) 匿名クラスでは使用できないため、使用するすべてのコードはsuperclass に配置され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ません (クラスへの参照がないため)。2 回目の実行では、クラスが明示的にコンパイルされるため、期待どおりの結果が得られます。

あなたの問題に私の解決策を当てはめるのを助けるために、Nimbus のルック アンド フィールを設定する正しい方法へのリンクを次に示します。

Nimbus のルック アンド フィール

于 2010-08-11T14:56:50.433 に答える
1

BCEL を使用して動的サブクラスをその場で生成します。

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

于 2010-08-07T20:04:56.703 に答える
0

Classクラスを使用してそれを行うことができます。

いいえ:

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

上記の文は、現在のクラスパスで見つからない場合、ClassNotFoundException をスローします。例外がスローされない場合は、newInstance()メソッドを使用してyour.package.YourClassクラスcのオブジェクトを作成できます。特定のコンストラクターを呼び出す必要がある場合は、メソッドを使用してコンストラクターを取得し、それを使用して新しいインスタンスを作成できます。getConstructors

于 2010-08-07T19:44:20.210 に答える
-1

ええと、拡張したいクラスをコンパイル時のクラスパスに入れ、通常どおりサブクラスを記述し、実行時にサブクラスのロードを明示的にトリガーし、リンカーによってスローされた例外を処理して、スーパークラスがない?

于 2010-08-07T21:04:43.743 に答える