Следующий код должен решить вашу проблему. Класс 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
не равно нулю, вызовите 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)
.
Что происходит со строкой s
и строкой 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:
Нимб Смотри и чувствуй