Можно ли загрузить один из двух разных классов в Java с одинаковым именем? - PullRequest
3 голосов
/ 15 февраля 2011

У меня много кода, который вызывает статические методы в Foo, такие как "Foo.method ()".У меня есть две разные реализации Foo, и я хотел бы использовать одну или другую в зависимости от обстоятельств.В psuedocode:

Файл Foo1.java

class Foo1 implements Foo {
  public static int method() {
    return 0;
  }
} 

Файл Foo2.java

class Foo2 implements Foo {
  public static int method() {
    return 1;
  }
}

Файл Main.java

if(shouldLoadFoo1()) {
  Foo = loadClass("Foo1");
} else {
  Foo = loadClass("Foo2");
}

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

Ответы [ 6 ]

3 голосов
/ 15 февраля 2011

По сути, у вас есть два класса с одинаковым интерфейсом, но разными реализациями. Не лучше ли сделать это с помощью интерфейса?

в вашем основном классе, в зависимости от обстоятельств, с которыми вы будете создавать свой класссоответствующий экземпляр.

FooInterface foo;
MainClass (FooInteface foo, other fields) {
   this.foo = foo;
}


....

, затем просто используйте foo из них.

Другой способ заключается в использовании AspectJ, определяющего срез точки при каждом вызове Foo.method, в рекомендации дляу пункта сокращения есть Ваш if (shouldLoadFoo1()) { Foo1.method()} и т.д ..

2 голосов
/ 15 февраля 2011

Я не уверен, что я полностью понимаю проблему здесь (я вижу, что у многих есть эта проблема), но позвольте мне попытаться помочь.Если ваша проблема сводилась только к использованию соответствующего метода функции (), вы можете создать служебную функцию, которая в зависимости от экземпляра данного класса будет вызывать соответствующий метод, например

private static int getResultOfFoo(Foo foo)
{
int res = -1;
if(foo instanceof Foo1)
    res = Foo1.method();
else res = Foo2.method();
return res;
}

В противном случае я согласен сСтивен С: «Ну, тогда посмотри на мой ответ. Это самое близкое, что ты можешь получить в Java».

2 голосов
/ 15 февраля 2011

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

Следующим наиболее чистым способом является шаблон синглтона, т.е.объявлять:

public abstract class Foo {
    protected abstract void doSomeMethod();

    // populated at startup using whatever logic you desire
    public static Foo instance; 

    public static void someMethod() {
        instance.doSomeMethod();
    }
}

По-настоящему хакерский способ решить вашу проблему - это то, о чем вы просите, то есть иметь два разных файла классов для одного и того же класса, и решить во время выполнения, какой из них использовать.Для этого вы должны разделить ваш проект на 4 разных jar-файла:

  • loader.jar, который определяет используемый путь к классам и создает загрузчик классов для реального приложения.Классы в loader.jar не должны ссылаться на Foo.
  • foo1.jar, который содержит одну реализацию для Foo
  • foo2.jar, которая содержит другую реализацию для Foo
  • common.jarкоторый содержит все остальное

Loader.jar будет содержать метод начальной загрузки, например:

void bootstrap() {
    URL commonUrl = // path to common.jar
    URL fooUrl;
    if (shouldUseFoo1()) {
        fooUrl = // path to Foo1.jar
    } else {
        fooUrl = // path fo Foo2.jar
    }
    URL[] urls = {fooUrl, commonUrl};
    ClassLoader loader = new UrlClassLoader(urls);
    Class<?> mainClass = loader.loadClass("my.main");
    mainClass.newInstance(); // start the app by invoking a constructor
}
2 голосов
/ 15 февраля 2011

То, что вы написали, не имеет смысла с лингвистической точки зрения. Foo - это тип, а тип не является переменной и не может появляться в LHS присвоения. Вы не можете рассматривать тип как значение в Java ... язык не допускает этого.

Самое близкое, что вы можете получить к тому, что вы пытаетесь сделать, это что-то вроде этого:

Class fooClass;
if (loadFoo1) {
    fooClass = Class.forName("some.pkg.Foo1");
} else {
    fooClass = Class.forName("some.pkg.Foo2");
}
Foo foo = (Foo) fooClass.newInstance();  // using the no-args constructor

(я исключил обработку исключений ...)

Обратите внимание, что fooClass будет экземпляром класса Class, который предоставляет дескрипторы времени выполнения, которые используются для рефлексивного выполнения операций. Мы на самом деле не присваиваем тип. Мы присваиваем объект, который «обозначает» тип ... ограниченным образом.


ОДНАКО ... если вам не нужен для использования динамической загрузки, вы не должны его использовать. Другими словами, если основная проблема, которую вы пытаетесь решить, это создание экземпляров классов, которые могли бы быть статически загружены, тогда лучше использовать шаблон фабрики; см. ответ @ andersoj, например.


UPDATE

Я только что понял, что вы, вероятно, пытаетесь сделать здесь. То есть вы пытаетесь найти способ выбора между различными статическими методами (т. Е. Foo1.method() и Foo2.method()) без явного присвоения имен классам в точке, где выполняется вызов.

Опять же, то, что вы пытаетесь сделать, просто не будет работать в Java:

  • Вы не можете объявить статический метод в интерфейсе.
  • Вы не можете вызывать статический метод в классе реализации через интерфейс.
  • Статические вызовы методов не отправляются в Java. Они связаны статически.

Есть способ сделать что-то вроде этого, используя отражение; например,

Class fooClass;

// Load one or other of the classes as above.

Method m = fooClass.getDeclaredMethod("method");
Integer res = (Integer) m.invoke(null);

(Как и прежде, я исключил обработку исключений)

Еще раз, вам было бы гораздо лучше сделать это, не прибегая к динамической загрузке и отражению. Простой подход заключается в создании вспомогательного метода, подобного этому, в классе некоторых утилит:

public static int method() {
    return useFoo1 ? Foo1.method() : Foo2.method();
}

Более того, сделайте это OO-способом: объявите method в интерфейсе Foo как метод экземпляра, создайте одиночный или внедренный экземпляр Foo1 или Foo2 и полагайтесь на полиморфизм.

Но дело в том, что НЕТ СПОСОБА избегать изменения всех мест в вашей кодовой базе, где вызывается method() ... если вы хотите иметь возможность выбирать между Foo1.method и Foo2.method во время выполнения .

2 голосов
/ 15 февраля 2011

Вы можете использовать фабричный шаблон для этого.

static Foo makeMeAFoo()
{
  final Foo foo;
  if(shouldLoadFoo1()) {
    foo = new Foo1();
  } else {
    foo = new Foo2();
  }
  return foo;
}

Что, я думаю, вы и просите.Хотя мне самому больше нравится предложение hhafez.

(Обратите внимание, мой ответ теперь OBE b / c, спрашивающий перевел методы на статические, а не на методы экземпляров. Тем не менее, тон других отвечающих хороший ...решение этой проблемы с помощью явной загрузки классов только потому, что вы хотите выбрать конкретные статические методы, - это клочок.)

1 голос
/ 15 февраля 2011

В вашем примере на самом деле у вас есть не две разные версии класса Foo, а две разные реализации интерфейса Foo, что хорошо в большинстве случаев.(Они даже могут существовать параллельно друг другу.)

Можно загрузить несколько классов с одним и тем же именем, но они должны быть загружены разными загрузчиками классов.Это также означает, что у вас не может быть третьего класса, ссылающегося на него по имени и затем использующего один или другой (без третьего класса, также находящегося на двух загрузчиках классов).

Иногда может быть целесообразно иметь разные версиикласса (с тем же внешним интерфейсом) для разных конфигураций, где он будет использоваться (например, «на стороне клиента» / «на стороне сервера», когда от него зависит какой-то общий класс в обоих модулях), и в редких случаях вы быиметь оба модуля в одной и той же виртуальной машине одновременно, но в большинстве случаев лучше использовать подход «один интерфейс и несколько реализующих классов».

...