是否可以使用相同的名称加载Java中的两个不同类中的一个?

时间:2011-02-14 23:33:43

标签: java metaprogramming

我有很多代码在Foo上调用静态方法,如“Foo.method()”。我有两种不同的Foo实现,并希望根据具体情况使用其中一种。在伪代码中:

File Foo1.java

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

文件Foo2.java

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

File Main.java

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

这可以通过Java元编程实现吗?我无法完全围绕所有动态类加载文档。如果没有,那么做我想做的最好的方法是什么?

6 个答案:

答案 0 :(得分:3)

基本上你有两个具有相同接口但实现不同的类,使用接口做它不是更好吗?

在您的主类中,具体取决于您使用适当的实例构建类的环境。

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


....

然后只使用他们的foo。

另一种方法是使用AspectJ,在每个Foo.method调用上定义一个切点,在切点的建议中有你的if (shouldLoadFoo1()) { Foo1.method()}等。

答案 1 :(得分:2)

您可以使用工厂模式执行此操作。

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

我认为你要求的是什么。虽然我更喜欢哈菲兹的建议。

(注意我的回答现在是OBE b / c,提问者将方法转移到静态而不是实例方法。然而,其他回答者的语气很好......通过显式类加载解决这个问题只是因为你想选择特定的静态方法是一种kludge。)

答案 2 :(得分:2)

从语言学的角度来看,你所写的内容没有意义。 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的一个实例,它提供用于反射执行操作的运行时句柄。我们实际上并没有分配类型。我们正在以有限的方式指定一个“表示”类型的对象。


HOWEVER ...如果您不需要使用动态加载,则不应使用它。换句话说,如果您尝试解决的基础问题是创建 静态加载的类的实例,那么最好使用工厂模式;例如,请参阅@ andersoj的答案。


<强>更新

我刚刚想出你可能在这里尝试做什么。也就是说,您试图找出一种在不同的静态方法(即Foo1.method()Foo2.method())之间进行选择的方法,而无需在调用时明确命名类。制成。

同样,你要做的事情根本不适用于Java:

  • 您无法在界面中声明静态方法。
  • 您不能通过接口在实现类中调用静态方法。
  • Java中没有“调度”静态方法调用。他们是静态约束的。

有一种方法可以使用反射做大致相似的事情; e.g。

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作为实例方法,创建单个或Foo1Foo2的注入实例,并依赖于多态性。

但需要注意的是,没有办法避免更改代码库中调用method()的所有位置...如果您希望能够在Foo1.method和{之间进行选择{1}}在运行时。

答案 3 :(得分:2)

交换实现的典型方法是使用非静态方法和多态,通常使用依赖注入来告知依赖代码要使用的实现。

下一个最简洁的方法是单身模式,即声明:

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

解决问题的真正hacky方法就是你要求的,即为同一个类提供两个不同的类文件,并在运行时决定使用哪一个。为此,您可以将项目分成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
}

答案 4 :(得分:2)

我不确定我是否完全理解这里的问题(我看到很多人都有这个问题),但让我试着帮忙。 如果您的问题只是使用适当的函数方法(),您可以创建一个实用程序函数,根据给定类的实例将调用适当的方法,例如

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

否则,我同意Stephen C的观点:“那么,请看我的答案。这是你可能用Java最接近的。”

答案 5 :(得分:1)

在你的例子中,你实际上没有两个不同版本的类Foo,而是两个不同的Foo接口实现,在大多数情况下都没问题。 (它们甚至可以彼此平行存在。)

可以加载多个同名的类,但它们必须由不同的类加载器加载。这也意味着你不能让第三个类按名称引用它,然后使用其中一个(没有第三个类也在两个类加载器上)。

有时为不同的配置使用不同版本的类(具有相同的外部接口)可能是明智的(例如“在客户端”/“在服务器端”,当两者中的一些公共类时模块依赖于它),并且在极少数情况下,您可以同时在同一个VM中同时使用这两个模块 - 但在大多数情况下,最好使用“一个接口和多个实现类”方法。