【问题标题】:Is this dynamic dispatch?这是动态调度吗?
【发布时间】:2024-01-08 23:00:01
【问题描述】:

这是动态调度吗:

abstract class A{
    public method Meth1(){
    //somecode
    }
}

class B extends A{
}

class C extends A{
}

完全在另一个班级:

Some_Method(A a){
    a.Meth1();
}

我不确定这是否是动态调度,因为两个子类的行为相同?

如果不是,如果行为是根据子类定义的,它会是动态调度吗?

【问题讨论】:

标签: java dynamic-method dynamic-dispatch


【解决方案1】:

是的,是的!

因为在 Java 中所有实例方法默认都是虚拟的。 (Can you write virtual functions / methods in Java?)

那么解析 a.Meth1() 需要在运行时完成。 请记住,您可以使用派生自 A 并覆盖该方法的类动态加载新 JAR。

【讨论】:

    【解决方案2】:

    这是动态分派,因为编译器在为调用类生成字节码时不能假设它知道所有类。

    运行时字节码解释器或 JIT 编译器都不会,至少因为类可以动态加载。

    在我看来,这让 java 除了使用动态调度之外别无选择,而不是“偷工减料”,即优化基类调用的调用。

    【讨论】:

      【解决方案3】:

      我不确定你所说的具体问题是什么(如果静态已知调用的方法仅在一个类中声明,则可能会有一些特定于实现的优化将绕过运行时类型检查),但实际上,动态分派允许在运行时确定Meth1 方法的实际实现。所以,即使现在BC都没有覆盖Meth1,以后如果被覆盖,动态调度会保证如果形式参数a的运行时类型是B,那么实际实现将是B 上的那个。 C 的情况类似。

      这与 Java 中的方法重载形成对比,其中实际方法是在编译时根据所使用参数的声明类型确定的。

      public class Overloading {
      
        public static class User {}
        public static class Admin extends User {}
      
        public static String foo(User user) {
          return "User specific method";
        }
      
        public static String foo(Admin admin) {
          return "Admin specific method";
        }
      
        // This will print "User specific method" two times, because both
        // user1 and user2 have same compile time type, i.e., User. Runtime
        // type does not matter.
        public static void main(String[] args) {
          User user1 = new User();
          System.out.println(foo(user1));
      
          User user2 = new Admin();
          System.out.println(foo(user2));
        }
      }
      

      【讨论】:

      • 其实我不同意“动态调度允许在运行时确定 Meth1 方法的实际实现”这句话。多态调度允许受控的动态性,虽然不是静态绑定的,但它是静态安全的(保证接收器方法存在)。
      • 所以你是说我的例子是动态调度,因为我传入参数 A a,其中 a 是 new B() 或 new C()?
      • “如果被调用的方法静态已知只在一个类中声明,则可能有一些特定于实现的优化将绕过运行时类型检查”这需要封闭的宇宙,这是太强的限制(即禁止使用 Dunamic 代理)。相反,您拥有的是单态优化和推测内联。查看 HotSpot 维基:wikis.sun.com/display/HotSpotInternals/PerformanceTechniques
      • @James,否 - 静态类型的多态调度。从技术上讲,您具有动态绑定,但这与动态调度无关(如果您不想更加困惑,最好忘记它)。 ——
      • @ddmitrov,我相信你所说的静态类型多态调度,其他人都称之为动态调度。 c2.com/cgi/wiki?DynamicDispatch
      【解决方案4】:

      动态分派是指根据实际而非声明的类型选择方法实现。 Java 不支持动态调度,除非通过反射。这是静态类型的多态调度。

      如果您加载了单个实现,JVM 将应用单态优化(产生非常快的调用),当 JVM 看到第二个实现传递给相同的代码时,该优化将被撤消。

      您可能听说过新的“invokedynamic”字节码,它在 JVM 中实现动态调度,但它旨在供其他 JVM 语言使用,Java 程序不会使用它,除非在生成字节码时使用它。

      [编辑]这是一个简单的例子:

      Collection<Integer> c = new ArrayList<Integer>(Arrays.asList(2, 1, 0));
      c.remove(2); // Collection.remove(E element) or List.remove(int idx)?
      assert c.equals(Arrays.asList(1, 0)); // passes in case of static dispatch
      assert c.equals(Arrays.asList(2, 1)); // fails - would pass in case of dynamic dispatch
      

      【讨论】:

      • 两者的唯一区别是参数1和2?我有点困惑,因为我的前任讲师告诉我动态调度在 Java 中是可能的,因为他们在讲座中提到了它的一个例子.....
      • @James T,Java 中确实可以进行单一动态调度。动态分派意味着调用方法的对象是在运行时确定的。
      • 你好,我写的是单个动态调度的例子吗?
      • Java 确实支持动态调度。 “Java 不支持动态调度”怎么说?
      • 对不起 James,Java 中不存在动态调度,所以我不能给你一个例子。我能想到的最好的例子是上面代码的反事实示例。你可以把它放在调试器中,然后在 Java(静态调度)和 Groovy(动态调度)下运行它。无论如何,一个断言会成功,另一个会失败。