开发者

Is it possible to load one of two different classes in Java with the same name?

开发者 https://www.devze.com 2023-02-10 17:18 出处:网络
I have a lot of code that calls static methods on Foo like \"Foo.method()\". I have two different implementations of Foo and would like to use one or the other depending on the circumstances. In psued

I have a lot of code that calls static methods on Foo like "Foo.method()". I have two different implementations of Foo and would like to use one or the other depending on the circumstances. In psuedocode:

File Foo1.java

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

File Foo2.java

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

File Main.java

if(shouldLoadFoo1()) {
  Foo = loadClass("开发者_Go百科Foo1");
} else {
  Foo = loadClass("Foo2");
}

Is this possible with Java metaprogramming? I can't quite wrap my head around all the dynamic class loading documentation. If not, what's the best way to do what I'm trying to do?


Essentially you have two classes with the same interface but different implementations,Wouldn't it be better to do it using an interface?

in your main class, depending on the circumstances you would construct your class with the appropriate instance.

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


....

then just use foo from them on.

Another way is to use AspectJ, define a point cut on every Foo.method call, in in the advice for the point cut have your if (shouldLoadFoo1()) { Foo1.method()} etc ..


The typical approach to exchanging implementations is to use a non-static method and polymorphism, typically using dependency injection to tell the depedent code the implementation to use.

The next cleanest way is the singleton pattern, i.e. to declare:

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();
    }
}

The really hacky way to solve your problem would be what you ask for, i.e. to have two different class files for the same class, and decide at runtime which one to use. To do that, you would seperate your project into 4 different jar files:

  • loader.jar that determines the classpath to use and constructs the classloader for the actual application. The classes in loader.jar must not reference Foo.
  • foo1.jar that contains one implementation for Foo
  • foo2.jar that contains another implementation for Foo
  • common.jar that contains everything else

Loader.jar would then contain a bootstrap method like:

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
}


I am not sure I fully understand the problem here (I see many has that issue), but let me try to help. If your problem was coming down just to using appropriate function method(), you could create a utility function that depending on an instance of a given class will call appropriate method, e.g.

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

Otherwise, I agree with Stephen C: "Well, see my answer then. That's the closest you are likely to get in Java."


What you have written doesn't make sense from a linguistic standpoint. Foo is an type, and a type is not a variable and cannot appear on the LHS of an assignment. You cannot treat a type as a value in Java ... the language doesn't allow it.

The closest that you can get to what you are trying to do is something like this:

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

(I've left out the exception handling ...)

Note that fooClass will be an instance of the class Class which provides runtime handles that are used for performing operations reflectively. We are NOT actually assigning a type. We are assigning an object that "denotes" a type ... in a limited fashion.


HOWEVER ... if you don't need to use dynamic loading you should not use it. In other words, if the underlying problem that you are trying to solve is creating instances of classes that could be statically loaded, then it is better to use the factory pattern; see @andersoj's answer for example.


UPDATE

I just figured out what you are probably trying to do here. That is, you are trying to figure out a way to choose between different static methods (i.e. Foo1.method() and Foo2.method()) without explicitly naming the classes at the point where the call is made.

Again, what you are trying to do simply won't work in Java:

  • You cannot declare a static method in an interface.
  • You cannot call a static method in an implementation class via the interface.
  • Static method calls are not "dispatched" in Java. They are bound statically.

There is a way to do something roughly like this using reflection; e.g.

Class fooClass;

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

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

(As before, I've left out the exception handling)

Once again you would be much better off doing this without resorting to dynamic loading and reflection. The simple approach would be to create a helper method like this in some utilities class:

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

Better still, do it the OO way: declare method in the Foo interface as a instance method, create a singleton or an injected instance of Foo1 or Foo2, and rely on polymorphism.

But the take away is that there is NO WAY to avoid changing all of the places in your codebase where method() is called ... if you want to be able to choose between Foo1.method and Foo2.method at runtime.


You can use a factory pattern to do this.

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

Which is I think what you're asking for. Though I like hhafez' suggestion better myself.

(Note my answer is now OBE b/c the questioner shifted the methods to be static rather than instance methods. Nevertheless, the tone of other answerers is good... solving this problem by explicit classloading just because you want to select specific static methods is a kludge.)


In your example you in fact have not two different versions of class Foo, but two different implementations of the interface Foo, which is fine in most cases. (They even can exist parallel to each other.)

It is possible to load multiple classes of the same name, but they have to be loaded by different classloaders. This also means that you can't have a third class referencing it by name and then using one or the other (without the third class also being on two classloaders).

Sometimes it may be sensible to have different versions of a class (with same external interface) for different configurations where it would be used (such as "on client side" / "on server side", when some common class in both modules depends on it), and in rare cases you would have both modules in the same VM at the same time - but in most cases it would be better to use the "one interface and multiple implementing classes" approach instead.

0

精彩评论

暂无评论...
验证码 换一张
取 消