【问题标题】:Is it possible to load one of two different classes in Java with the same name?是否可以在 Java 中加载具有相同名称的两个不同类之一?
【发布时间】:2011-06-27 06:44:39
【问题描述】:

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

文件 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 元编程实现吗?我无法完全理解所有动态类加载文档。如果不是,那么做我想做的事情的最佳方法是什么?

【问题讨论】:

  • 为什么需要动态加载类而不是创建单独的 Foo1 和 Foo2 实例?
  • 我猜他的问题是 Foo1 和 Foo2 的方法是静态的。这有点奇怪,对于同一个操作有不同的实现通常意味着他不应该使用静态方法,但是对于这个通用示例很难判断。史蒂夫,这些课程实际上是做什么的?如果他们的合同相同,你为什么不使用接口?
  • 你确定method() 是静态的吗?从提供的代码来看,它看起来不是那样的。
  • @Hunter2:假设 Foo 是一个日志类。 Foo1 记录到磁盘,Foo2 记录到网络。有一种静态方法“log”。
  • @Steve,java 中的类是通过引用匹配的,但它们是通过名称解析的。

标签: java metaprogramming


【解决方案1】:

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

在您的主类中,具体取决于您将使用适当的实例构造类的情况。

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


....

然后从他们开始使用 foo。

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

【讨论】:

  • 问题是有很多使用 Foo 的遗留代码,我无法更改所有代码的 API 以在 FooInterface 中传递。我可以将所有“Foo.method()”更改为“Foo.getRealFoo().method()”或类似的东西,但我试图隐藏有多个 Foo 实现的事实,因为它并不重要到调用 Foo 的代码。
  • 老实说,这是最干净的方法,您可以判断重构与收益需要多少努力。
  • 看看我的回答 Re AspectJ 以在您的代码中编织此逻辑,同时仍将其与您的代码分开。
  • 必须支持关于重构是最干净的方式的评论。根据我的经验,解决这个问题很快就会比修复它更难。原作者所做的是编写代码这样你就不能做你想做的事。如果这不是他们的意图,那么,他们不是一个好的程序员。如果那他们的意图,那么您只需要诚实地投入工作来修复它,因为从那时起世界已经发生了变化。从你的新 FooInterface 而不是 Foo2 开始,然后慢慢写出你需要新功能的旧 Foo。
【解决方案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
}

【讨论】:

    【解决方案3】:

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

    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 中可能得到的最接近的答案。”

    【讨论】:

      【解决方案4】:

      从语言的角度来看,您所写的内容没有意义。 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 的答案。


      更新

      我刚刚弄清楚您可能想在这里做什么。也就是说,您正在尝试找出一种在不同的静态方法(即Foo1.method()Foo2.method())之间进行选择的方法,而无需在调用时显式命名类。 p>

      同样,您尝试做的事情在 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 的方式来做:在Foo 接口中声明method 作为实例方法,创建Foo1Foo2 的单例或注入实例,并依赖多态性。

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

      【讨论】:

      • 我知道这没有意义,我说它是伪代码。我相信它传达了我想要表达的想法,这就是我使用它的原因。
      • @Steve - 问题在于它没有传达你想要做的事情。目前尚不清楚您是在尝试加载类还是创建实例。
      • 我正在尝试加载一个类,我不需要实例化这些类。
      • 那么,看我的回答。这是您在 Java 中可能得到的最接近的结果。
      • 同意 Stephen C 的观点——如果您的目标不是实例化,为什么还要加载一个类?有一些用例,但它们通常定义得非常清晰且重点突出(而且通常令人不快)。为了未来的读者,您可以考虑加强您的问题以明确用例是什么。
      【解决方案5】:

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

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

      我认为你要的是什么。虽然我自己更喜欢 hhafez 的建议。

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

      【讨论】:

        【解决方案6】:

        在您的示例中,实际上您没有两个不同版本的类 Foo,而是接口 Foo 的两个不同实现,这在大多数情况下都很好。 (它们甚至可以相互平行存在。)

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

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2017-09-26
          • 2011-10-16
          • 1970-01-01
          • 2018-04-03
          • 2014-01-12
          • 1970-01-01
          • 1970-01-01
          • 2015-10-19
          相关资源
          最近更新 更多