search
HomeJavajavaTutorialJava in-depth understanding of dynamic binding


In object-oriented programming languages, polymorphism is the third basic feature after data abstraction and inheritance. Polymorphism separates the interface and implementation from another perspective by separating what to do and how to do it. When we first come into contact with the word polymorphism, we may be confused by the word itself. If we change polymorphism to "dynamic binding", I believe many people will be able to understand its deeper meaning. Usually, we call dynamic binding late binding and runtime binding.

(1) Method call binding

1. Binding concept

Usually, we call a method in the same The association of a method body is called a binding. If binding is performed before program execution, we call this binding method early binding. In procedural languages, such as C, this method is the default and only one. If we use early binding in Java, it is very likely that the compiler will be confused as to which method to bind in this huge inheritance implementation system. The solution is dynamic binding. This late binding method binds according to the type of the object at runtime.

In java, dynamic binding is the default behavior. However, in a class, ordinary methods will use this dynamic binding method, and there are also some situations where dynamic binding does not occur naturally.

2.final modification

If a property is modified by final, the meaning is: it cannot be changed after initialization.
If a method is modified by final, it means that it cannot be overridden. We often like to say this from a macro perspective, but why can't our methods that are actually modified by final be overridden? Because the final modifier actually turns off dynamic binding. In Java, the content modified by final cannot be dynamically bound. Without dynamic binding, there is no concept of polymorphism, and naturally it cannot be overridden.

3. "Override" private methods

In fact, we rarely set methods as private. If we "overwrite" the private method, what we actually get is a new method. It has nothing to do with the parent class at all. You should pay attention to this. You may be asked during the interview: "Overriding" the private methods of the parent class in the subclass is allowed without reporting an error. They are just two completely unrelated methods.

4. Domains and static methods

After we understand polymorphism, we may think that all things can occur polymorphically. Actually not, If we directly access a domain, this access will be parsed during compilation, we can refer to the following example:

package Polymorphic;/**
 * 
 * @author QuinnNorris
 * 域不具有多态性
 */public class polymorphics {

    /**
     * @param args
     */
    public static void main(String[] args) {        // TODO Auto-generated method stub
        Super sup = new Sub();
        System.out.println("sup.field = " + sup.field + ", sup.getField() = "
                + sup.getField());
        Sub sub = new Sub();
        System.out.println("sub.field = " + sub.field + ", sub.getField() = "
                + sub.getField() + ", sub.getSuperField() = "
                + sub.getSuperField());
    }

}

class Super {    public int field = 0;    public int getField() {        return field;
    }
}

class Sub extends Super {    public int field = 1;    public int getField() {        return field;
    }    public int getSuperField() {        return super.field;
    }
}

Output result:

 sup.field = 0, sup.getField() = 1 

  sub.field = 1, sub.getField() = 1, sub.getSuperField() = 0


This example tells us that when we call a method, the main body to choose which method to execute is dynamically selected at runtime. But when we directly access the instance field, the compiler directly accesses it according to the type represented by this object. The same situation exists for static methods. So we can make this summary:

  1. Normal method: dynamic binding according to the type of object entity

  2. Domain and static Method: Early binding based on the type represented by the object

#In layman's terms, for ordinary methods, we look at the type after new; for domain and static methods, we look at = front What type is declared.
Although this seems to be a very confusing question. But in practice, this never (or rarely) happens. First, programmers who don't set instance fields to private have basically been fired (instance fields are rarely modified to be public). Secondly, we rarely set the fields we create in the subclass to the same name as the parent class.

(2) Constructor and polymorphism

Generally, the constructor is a very unique existence. The same is true when it comes to polymorphism. Although constructors are not polymorphic (in fact they are decorated with static, although the static is implicitly declared), it is still necessary to understand how constructors work.

1. The calling sequence of constructors

The constructor of the parent class is always called during the call of the constructor of the subclass, and according to inheritance The chaining is gradually upward so that the constructor of each parent class can be called correctly. This is necessary because the constructor has a special task, checking whether the object has been constructed correctly. Subclass methods can only access their own members, not members of the parent class. Only the base class constructor has the appropriate permissions to initialize its own elements. Therefore, every constructor must be called, otherwise a correct and complete object cannot be constructed.

package Polymorphic;

public class Father {

    /**
     * @param args
     */
    public static void main(String[] args) {        // TODO Auto-generated method stub
        new F();
    }

}class A {
    A() {
        System.out.println("A");
    }
}class B extends A {
    B() {
        System.out.println("B");
    }
}class C extends B {
    C() {
        System.out.println("C");
    }
}class D {
    D() {
        System.out.println("D");
    }
}class E {
    E() {
        System.out.println("E");
    }
}class F extends C {
    private D d = new D();    private E e = new E();

    F() {
        System.out.println("F");
    }
}

Output results:

 A 

  B 

  C 

  D 

  E 

  F


##The seemingly accidental output of "ABCDEF" is actually our Carefully arranged.

This example very intuitively illustrates the constructor calling rule, which has the following three steps:

  1. 调用父类构造器。这个步骤会反复递归进去,直到最祖先的类,依次向下调用构造器。

  2. 按声明顺序调用成员的初始化构造器方法。

  3. 调用子类构造器的主体。

可能我说了这个顺序,大家马上就会想到super。是的没错,super()确实可以显示的调用父类中自己想要调用的构造方法,但是super()必须放在构造器的第一行,这个是规定。我们的顺序是没有任何问题的,或者说其实在F的构造器中第一句是super()。只不过我们默认省略了。

(三)协变返回类型特性

java在se5中添加了协变返回类型,它表示在子类中的被覆盖方法可以返回父类这个方法的返回类型的某种子类

package Polymorphic;/**
 * 
 * @author QuinnNorris
 * 协变返回类型
 */public class covariant {

    /**
     * @param args
     */
    public static void main(String[] args) {        // TODO Auto-generated method stub

        A b = new B();
        b.getC().print();

        A a = new A();
        a.getC().print();
    }

}class A{
    public C getC() {        return new C();
    }
}class B extends A{
    public D getC(){        return new D();
    }
}class C{
    public void print(){
        System.out.println("C");
    }
}class D extends C{
    public void print(){
        System.out.println("D");
    }
}

输出结果:

 D 

  C


在上面的例子中,D类是继承于C类的,B类是继承于A类的,所以在B类覆盖的getC方法中,可以将返回类型协变成,C类的某个子类(D类)的类型。

(四)继承设计

通常,继承并不是我们的首选,能用组合的方法尽量用组合,这种手段更灵活,如果你的代码中is-a和is-like-a过多,你就应该考虑考虑是不是该换成has-a一些了。一条通用的准则是:用继承表达行为间的差异,并用字段表达状态上的变化

而且在用继承的时候,我们会经常涉及到向上转型和向下转型。在java中,所有的转型都会得到检查。即使我们只是进行一次普通的加括号的类型转换,在进入运行期时仍然会对其进行检查,以便保证它的确是我们希望的那种类型。如果不是,就返回一个ClassCastException(类转型异常)。这种在运行期间对类型进行检查的行为称作”运行时类型识别(RTTI)“。

在面向对象的程序设计语言中,多态是继数据抽象和继承之后的第三种基本特性。多态通过分离做什么和怎么做,从另一个角度将接口和实现分离开来。在一开始接触多态这个词的时候,我们或许会因为这个词本身而感到困惑,如果我们把多态改称作“动态绑定”,相信很多人就能理解他的深层含义。通常的,我们把动态绑定也叫做后期绑定,运行时绑定。

(一)方法调用绑定

1.绑定概念

通常,我们将一个方法调用同一个方法主体关联起来称作绑定。如果在程序执行前进行绑定,我们将这种绑定方法称作前期绑定。在面向过程语言中,比如c,这种方法是默认的也是唯一的。如果我们在java中采用前期绑定,很有可能编译器会因为在这庞大的继承实现体系中去绑定哪个方法而感到迷惑。解决的办法就是动态绑定,这种后期绑定的方法,在运行的时候根据对象的类型进行绑定。

在java中,动态绑定是默认的行为。但是在类中,普通的方法会采用这种动态绑定的方法,也有一些情况并不会自然的发生动态绑定。

2.final修饰

如果一个属性被final修饰,则含义是:在初始化之后不能被更改。
如果一个方法被final修饰,含义则是不能被覆盖。我们常常喜欢从宏观的角度这样说,但是我们真正的被final修饰的方法为什么不能被覆盖呢?因为final修饰词其实实际上关闭了动态绑定。在java中被final修饰的内容不能采用动态绑定的方法,不能动态绑定就没有多态的概念,自然也就不能被覆盖。

3.“覆盖”私有方法

其实我们很少把方法设定为私有。如果我们将private方法“覆盖”掉,其实我们获得的只是一个新的方法。完全和父类没关系了。这一点要注意,或许面试的时候会被问到:在子类中“覆盖”父类私有方法是被允许而不报错的,只不过完全是两个没关系的方法罢了。

4.域与静态方法

当我们了解了多态性之后可能会认为所有的事物都是可以多态地发生。其实并不是,如果我们直接访问某个域,这个访问会在编译期进行解析,我们可以参考下面的例子:

package Polymorphic;/**
 * 
 * @author QuinnNorris
 * 域不具有多态性
 */public class polymorphics {

    /**
     * @param args
     */
    public static void main(String[] args) {        // TODO Auto-generated method stub
        Super sup = new Sub();
        System.out.println("sup.field = " + sup.field + ", sup.getField() = "
                + sup.getField());
        Sub sub = new Sub();
        System.out.println("sub.field = " + sub.field + ", sub.getField() = "
                + sub.getField() + ", sub.getSuperField() = "
                + sub.getSuperField());
    }

}

class Super {    public int field = 0;    public int getField() {        return field;
    }
}

class Sub extends Super {    public int field = 1;    public int getField() {        return field;
    }    public int getSuperField() {        return super.field;
    }
}

输出结果:
 

sup.field = 0, sup.getField() = 1 

  sub.field = 1, sub.getField() = 1, sub.getSuperField() = 0


这个例子告诉我们,当我们调用一个方法时,去选择执行哪个方法的主体是运行时动态选择的。但是当我们直接访问实例域的时候,编译器直接按照这个对象所表示的类型来访问。于此情况完全相同的还有静态方法。所以我们可以做出这种总结:

  1. 普通方法:根据对象实体的类型动态绑定

  2. 域和静态方法:根据对象所表现的类型前期绑定

通俗地讲,普通的方法我们看new后面的是什么类型;域和静态方法我们看=前面声明的是什么类型。
尽管这看来好像是一个非常容易让人混悬哦的问题。但是在实践中,实际上从来(或者说很少)不会发生。首先,那些不把实例域设置为private的程序员基本上已经全都被炒鱿鱼了(实例域很少被修饰成public)。其次我们很少会将自己在子类中创建的域设置成和父类一样的名字。

(二)构造器与多态

通常,构造器是一个很独特的存在。涉及到多态的时候也是如此。尽管构造器并不具有多态性(实际上他们是有static来修饰的,尽管该static是被隐式声明的),但是我们还是有必要理解一下构造器的工作原理。

1.构造器的调用顺序

父类的构造器总是在子类构造器调用的过程中被调用,而且按照继承层次逐渐向上的链接,以使每个父类的构造器都能被正确的调用。这样做是很有必要的,因为构造器有一项特殊的任务,检查对象是否被正确的构造。子类方法只能访问自己的成员,不能访问父类中的成员。只有基类的构造器才具有恰当的权限对自己的元素进行初始化。因此必须要让每个构造器都能得到调用,否则不能构造出正确的完整的对象。

package Polymorphic;

public class Father {

    /**
     * @param args
     */
    public static void main(String[] args) {        // TODO Auto-generated method stub
        new F();
    }

}class A {
    A() {
        System.out.println("A");
    }
}class B extends A {
    B() {
        System.out.println("B");
    }
}class C extends B {
    C() {
        System.out.println("C");
    }
}class D {
    D() {
        System.out.println("D");
    }
}class E {
    E() {
        System.out.println("E");
    }
}class F extends C {
    private D d = new D();    private E e = new E();

    F() {
        System.out.println("F");
    }
}

输出结果:

 A 

  B 

  C 

  D 

  E 

  F


看似偶然的“ABCDEF”的输出结果,实际上是我们精心安排的。
这个例子非常直观的说明了构造器的调用法则,有以下三个步骤:

  1. 调用父类构造器。这个步骤会反复递归进去,直到最祖先的类,依次向下调用构造器。

  2. 按声明顺序调用成员的初始化构造器方法。

  3. 调用子类构造器的主体。

可能我说了这个顺序,大家马上就会想到super。是的没错,super()确实可以显示的调用父类中自己想要调用的构造方法,但是super()必须放在构造器的第一行,这个是规定。我们的顺序是没有任何问题的,或者说其实在F的构造器中第一句是super()。只不过我们默认省略了。

(三)协变返回类型特性

java在se5中添加了协变返回类型,它表示在子类中的被覆盖方法可以返回父类这个方法的返回类型的某种子类

package Polymorphic;/**
 * 
 * @author QuinnNorris
 * 协变返回类型
 */public class covariant {

    /**
     * @param args
     */
    public static void main(String[] args) {        // TODO Auto-generated method stub

        A b = new B();
        b.getC().print();

        A a = new A();
        a.getC().print();
    }

}class A{
    public C getC() {        return new C();
    }
}class B extends A{
    public D getC(){        return new D();
    }
}class C{
    public void print(){
        System.out.println("C");
    }
}class D extends C{
    public void print(){
        System.out.println("D");
    }
}

输出结果:

 D 

  C


在上面的例子中,D类是继承于C类的,B类是继承于A类的,所以在B类覆盖的getC方法中,可以将返回类型协变成,C类的某个子类(D类)的类型。

(四)继承设计

通常,继承并不是我们的首选,能用组合的方法尽量用组合,这种手段更灵活,如果你的代码中is-a和is-like-a过多,你就应该考虑考虑是不是该换成has-a一些了。一条通用的准则是:用继承表达行为间的差异,并用字段表达状态上的变化

而且在用继承的时候,我们会经常涉及到向上转型和向下转型。在java中,所有的转型都会得到检查。即使我们只是进行一次普通的加括号的类型转换,在进入运行期时仍然会对其进行检查,以便保证它的确是我们希望的那种类型。如果不是,就返回一个ClassCastException(类转型异常)。这种在运行期间对类型进行检查的行为称作”运行时类型识别(RTTI)“。

 以上就是java深入理解动态绑定的内容,更多相关内容请关注PHP中文网(www.php.cn)!


Statement
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
How does the JVM manage garbage collection across different platforms?How does the JVM manage garbage collection across different platforms?Apr 28, 2025 am 12:23 AM

JVMmanagesgarbagecollectionacrossplatformseffectivelybyusingagenerationalapproachandadaptingtoOSandhardwaredifferences.ItemploysvariouscollectorslikeSerial,Parallel,CMS,andG1,eachsuitedfordifferentscenarios.Performancecanbetunedwithflagslike-XX:NewRa

Why can Java code run on different operating systems without modification?Why can Java code run on different operating systems without modification?Apr 28, 2025 am 12:14 AM

Java code can run on different operating systems without modification, because Java's "write once, run everywhere" philosophy is implemented by Java virtual machine (JVM). As the intermediary between the compiled Java bytecode and the operating system, the JVM translates the bytecode into specific machine instructions to ensure that the program can run independently on any platform with JVM installed.

Describe the process of compiling and executing a Java program, highlighting platform independence.Describe the process of compiling and executing a Java program, highlighting platform independence.Apr 28, 2025 am 12:08 AM

The compilation and execution of Java programs achieve platform independence through bytecode and JVM. 1) Write Java source code and compile it into bytecode. 2) Use JVM to execute bytecode on any platform to ensure the code runs across platforms.

How does the underlying hardware architecture affect Java's performance?How does the underlying hardware architecture affect Java's performance?Apr 28, 2025 am 12:05 AM

Java performance is closely related to hardware architecture, and understanding this relationship can significantly improve programming capabilities. 1) The JVM converts Java bytecode into machine instructions through JIT compilation, which is affected by the CPU architecture. 2) Memory management and garbage collection are affected by RAM and memory bus speed. 3) Cache and branch prediction optimize Java code execution. 4) Multi-threading and parallel processing improve performance on multi-core systems.

Explain why native libraries can break Java's platform independence.Explain why native libraries can break Java's platform independence.Apr 28, 2025 am 12:02 AM

Using native libraries will destroy Java's platform independence, because these libraries need to be compiled separately for each operating system. 1) The native library interacts with Java through JNI, providing functions that cannot be directly implemented by Java. 2) Using native libraries increases project complexity and requires managing library files for different platforms. 3) Although native libraries can improve performance, they should be used with caution and conducted cross-platform testing.

How does the JVM handle differences in operating system APIs?How does the JVM handle differences in operating system APIs?Apr 27, 2025 am 12:18 AM

JVM handles operating system API differences through JavaNativeInterface (JNI) and Java standard library: 1. JNI allows Java code to call local code and directly interact with the operating system API. 2. The Java standard library provides a unified API, which is internally mapped to different operating system APIs to ensure that the code runs across platforms.

How does the modularity introduced in Java 9 impact platform independence?How does the modularity introduced in Java 9 impact platform independence?Apr 27, 2025 am 12:15 AM

modularitydoesnotdirectlyaffectJava'splatformindependence.Java'splatformindependenceismaintainedbytheJVM,butmodularityinfluencesapplicationstructureandmanagement,indirectlyimpactingplatformindependence.1)Deploymentanddistributionbecomemoreefficientwi

What is bytecode, and how does it relate to Java's platform independence?What is bytecode, and how does it relate to Java's platform independence?Apr 27, 2025 am 12:06 AM

BytecodeinJavaistheintermediaterepresentationthatenablesplatformindependence.1)Javacodeiscompiledintobytecodestoredin.classfiles.2)TheJVMinterpretsorcompilesthisbytecodeintomachinecodeatruntime,allowingthesamebytecodetorunonanydevicewithaJVM,thusfulf

See all articles

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

Video Face Swap

Video Face Swap

Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Tools

MantisBT

MantisBT

Mantis is an easy-to-deploy web-based defect tracking tool designed to aid in product defect tracking. It requires PHP, MySQL and a web server. Check out our demo and hosting services.

EditPlus Chinese cracked version

EditPlus Chinese cracked version

Small size, syntax highlighting, does not support code prompt function

SublimeText3 Chinese version

SublimeText3 Chinese version

Chinese version, very easy to use

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

Powerful PHP integrated development environment

SecLists

SecLists

SecLists is the ultimate security tester's companion. It is a collection of various types of lists that are frequently used during security assessments, all in one place. SecLists helps make security testing more efficient and productive by conveniently providing all the lists a security tester might need. List types include usernames, passwords, URLs, fuzzing payloads, sensitive data patterns, web shells, and more. The tester can simply pull this repository onto a new test machine and he will have access to every type of list he needs.