【问题标题】:What is the reason behind Dynamic Method Resolution in a staticlly typed language like Java像Java这样的静态类型语言中动态方法解析背后的原因是什么
【发布时间】:2017-06-26 13:09:36
【问题描述】:

我对 Java 中引用变量的动态/静态类型和动态方法解析的概念有点困惑。

考虑:

public class Types {

    @Override
    public boolean equals(Object obj){
        System.out.println("in class Types equals()");
        return false;//Shut-up compiler!
    }

    public static void main(String[] args){
        Object typ = new Types();
        typ.equals("Hi");//can do this as String is a subclass of Object
    }
}

首先:引用变量typ的类型是Types,不是吗?!

那么 typ 具有 静态类型 对象和 动态类型 用于方法覆盖的类型的原因是什么?

第二:编译器没有足够的信息来调用正确的equals()吗?

如果类 Types 没有重写的 equals(),那么它可以调用 Object.equals() 方法。

在这种情况下,Types 类可以,编译器知道。

为什么不能像重载这样的早期绑定?为什么要交给 JVM?

【问题讨论】:

  • 抱歉,静态拼写错误。
  • 引用变量typ是静态类型Object和运行时类型Types。不,编译器只知道typObject

标签: java overriding


【解决方案1】:

这是面向对象编程的基本基石。

归结为多态性。如需进一步阅读,请参阅here

您绝对希望 子类 可以覆盖行为;因为这允许您保持客户端代码不变;但是您仍然可以引入新的/不同的行为;只需将 another 对象传递给该客户端代码即可。

客户端代码知道调用哪个方法;但是 dispatch 必须在运行时发生。仅仅是因为编译器可能知道(在大多数情况下)传入参数的确切类型。

【讨论】:

    【解决方案2】:

    您示例中的引用变量typ 的类型为Object

    它引用的对象是Types类型。

    编译器没有足够的信息知道该对象实际上是Types并使用Type.equals(Object)

    这可能会令人困惑。如果你写

    Object typ = new Types();
    

    那么编译器肯定知道typ 中的内容是Types。它只是将这些信息编译到代码中!

    但是,如果您决定将该行更改为

    Object type = new ObjectFactory().choose(choice).use(decision).build();
    

    好吧,现在你实际上并不知道build 会想出什么。这只有在运行时才知道,在 ObjectFactory 决定如何处理 choicedecision 的值之后。

    所以在这种情况下,编译器不知道。唯一可用的信息是变量的静态类型。如果编译器在使用 new 时的行为与使用上述工厂时不同,那将是非常糟糕的。

    现在看看这个场景:

    public class Types {
         public boolean equals( Object obj ) {
            // something
         }
    
         public boolean equals( String str ) {
            return false;
         }
    }
    
    public class Main {
    
        public static void main(String[] args) {
           Object typ = new Types();
           System.out.println( typ.equals("foo" ) );
        }
    }
    

    现在,在这种情况下,TypesMain 是两个独立的编译单元(不同的文件等)。假设编译器根据typTypes 的事实决定去。然后它将使用Types.equals(String)

    但现在您可以编辑Types,删除equals(String),然后只重新编译Types。当你运行程序时,你会得到一个“没有这样的方法异常”,因为编译器假定Types 有一个equals(String) 方法。因此,尽管您的程序是一个完全合法的 Java 程序,并且尽管在新的 Types 中有适当的匹配方法 equals(Object),但您的程序还是失败了。

    所以真正的编译器使用静态类型来判断一个方法是否存在以及给定静态参数是否合法,只有在运行时,JVM才能找到实际类型是什么,并调用适用的override那个方法。

    【讨论】:

      【解决方案3】:

      编译器只知道一个变量的Static Type,并用它来检查java中的一些约束。

      拳头:

      引用变量 typ 的类型是 Types,不是吗?!

      我认为编译器将变量typ 视为Object,当然typ 是真正指向Types 对象,因为每个类都是Object 的子类型,所以它可以分配给变量typ.

      第二:

      编译器没有足够的信息来调用正确的equals()吗?

      不。编译器只知道程序调用变量typ的方法equals(),但不知道typ指向Types实例或其他类型实例。那么真正的方法会是什么被调用在编译时是不确定的。

      看下面的代码:

          Object typ = new Main();
          Object o=new Object();
          typ.equals("Hi");//can do this as String is a subclass of Object
          o.equals("Hi");
      

      并查看 JVM 中的指令集:

          16: aload_1
          17: ldc           #42                 // String Hi
          19: invokevirtual #43                 // Method java/lang/Object.equals:(Ljava/lang/Object;)Z
          22: pop
          23: aload_2
          24: ldc           #42                 // String Hi
          26: invokevirtual #43                 // Method java/lang/Object.equals:(Ljava/lang/Object;)Z
      

      注意

      19: invokevirtual #43 // Method java/lang/Object.equals (Ljava/lang/Object;)Z

      26: invokevirtual #43 // Method java/lang/Object.equals:(Ljava/lang/Object;)Z

      typ.equals("Hi")o.equals("Hi")编译的指令集相同,但调用哪个方法取决于实际实例的方法引用表。

      #43 = Methodref          #41.#132      // java/lang/Object.equals:(Ljava/lang/Object;)Z
      

      #43是一个指向真实方法的方法引用。jvm中的每个实例都有一个方法引用表。如果类Override继承它的方法的某个方法,方法引用将更改为@ 987654341@方法。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-09-26
        • 2022-12-12
        • 2010-09-22
        相关资源
        最近更新 更多