Java: как динамически переопределить метод класса (класс в конечном итоге НЕ находится в пути к классам)?

Как я могу вызвать метод класса динамически + условно?
(Class в конечном итоге не в 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;
    }

  });
}

EDIT :
Теперь я отредактировал свой код, как было предложено, для перехвата NoClassDefFoundError с помощью try-catch. Это не удается. Я не знаю, если это вина OpenJDK. Я получаю InvocationTargetException , вызванный NoClassDefFoundError . Забавно, что я не могу поймать InvocationTargetException : оно все равно выбрасывается.

EDIT2: :
Обнаружена причина: я обернул SwingUtilities.invokeAndWait (...) вокруг тестируемого метода и тот самый вызов invokeAndWait выбрасывает NoClassDefFoundError при сбое загрузки Nimbus. 1278] EDIT3: :
Кто-нибудь может уточнить , где NoClassDefFoundError вообще может происходить? Потому что кажется, что это всегда вызывающий метод, а не фактический метод, который использует несуществующий класс.

10
задан java.is.for.desktop 11 August 2010 в 12:57
поделиться

5 ответов

Узнай, что класс доступен (во время выполнения)
Поместите использование в блок попытки ...

Если это не так, пропустите все это
... и оставьте блок 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);
   }
}
4
ответ дан 4 December 2019 в 03:15
поделиться

Для этого вы можете использовать класс Class .

I.E .:

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

Приведенное выше предложение вызовет исключение ClassNotFoundException, если оно не будет найдено в текущем пути к классам. Если исключение не возникло, вы можете использовать метод newInstance () в c для создания объектов класса your.package.YourClass . Если вам нужно вызвать конкретный конструктор, вы можете использовать метод getConstructors , чтобы получить его и использовать для создания нового экземпляра.

0
ответ дан 4 December 2019 в 03:15
поделиться

Эмм, не могли бы вы поместить класс, который хотите расширить, в путь класса времени компиляции, написать свой подкласс как обычно, а во время выполнения явно запустить загрузку подкласса и обработать любое исключение, созданное компоновщиком, которое указывает, что суперкласс отсутствует?

0
ответ дан 4 December 2019 в 03:15
поделиться

Используйте BCEL для создания динамического подкласса на лету.

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

1
ответ дан 4 December 2019 в 03:15
поделиться

Следующий код должен решить вашу проблему. Класс 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 , а класс A должен быть доступен во время компиляции класса 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");
        }
    }
}

Почему он компилируется и запускается?
Вы можете увидеть это в классе Main , импортируется только packageC.C , и нет ссылок на packageA.A или packageB.B . Если они есть, загрузчик классов вызовет исключение на платформах, на которых нет packageA.A , когда он попытается загрузить одну из них.

Как это работает?
В первом Class.forName () проверяется, доступен ли класс A на платформе. Если это так, попросите загрузчик классов загрузить класс B и сохранить полученный объект Class в classB . В противном случае ClassNotFoundException генерируется Class.forName () , и программа остается без класса A .

Затем, если classB не является нулевым, получите конструктор класса B , который принимает один объект C в качестве параметра. Сохраните объект Constructor в constructorB .

Затем, если constructorB не имеет значения NULL, вызовите 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 Look and Feel:

Nimbus Look and Feel

1
ответ дан 4 December 2019 в 03:15
поделиться
Другие вопросы по тегам:

Похожие вопросы: