搜尋
首頁Javajava教程java深入理解動態綁定

java深入理解動態綁定

Mar 01, 2017 am 11:05 AM


在物件導向的程式設計語言中,多型是繼資料抽象化和繼承之後的第三種基本特性。多態透過分離做什麼和怎麼做,從另一個角度將介面和實作分開。在一開始接觸多態性這個詞的時候,我們或許會因為這個詞本身而感到困惑,如果我們把多態改稱作“動態綁定”,相信很多人就能理解他的深層含義。通常的,我們把動態綁定也叫做後期綁定,運行時綁定。

(一)方法呼叫綁定

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)“。

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

(一)方法调用绑定

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)!


陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
在Java應用程序中緩解平台特定問題的策略是什麼?在Java應用程序中緩解平台特定問題的策略是什麼?May 01, 2025 am 12:20 AM

Java如何緩解平台特定的問題? Java通過JVM和標準庫來實現平台無關性。 1)使用字節碼和JVM抽像操作系統差異;2)標準庫提供跨平台API,如Paths類處理文件路徑,Charset類處理字符編碼;3)實際項目中使用配置文件和多平台測試來優化和調試。

Java的平台獨立性與微服務體系結構之間有什麼關係?Java的平台獨立性與微服務體系結構之間有什麼關係?May 01, 2025 am 12:16 AM

java'splatformentenceenhancesenhancesmicroservicesharchitecture byferingDeploymentFlexible,一致性,可伸縮性和便攜性。 1)DeploymentFlexibilityAllowsibilityAllowsOllowsOllowSorlowsOllowsOllowsOllowSeStorunonAnyPlatformwithajvM.2)penterencyCrossServAccAcrossServAcrossServiCessImplifififiesDeevelopmentandeDe

GRAALVM與Java的平台獨立目標有何關係?GRAALVM與Java的平台獨立目標有何關係?May 01, 2025 am 12:14 AM

GraalVM通過三種方式增強了Java的平台獨立性:1.跨語言互操作,允許Java與其他語言無縫互操作;2.獨立的運行時環境,通過GraalVMNativeImage將Java程序編譯成本地可執行文件;3.性能優化,Graal編譯器生成高效的機器碼,提升Java程序的性能和一致性。

您如何測試Java應用程序的平台兼容性?您如何測試Java應用程序的平台兼容性?May 01, 2025 am 12:09 AM

效率testjavaapplicationsforplatformcompatibility oftheSesteps:1)setUpautomatedTestingTestingActingAcrossMultPlatFormSusingCitoolSlikeSlikeJenkinSorgithUbactions.2)contuctualtemualtemalualTesteTESTENRETESTINGINREALHARTWARETOLEALHARDOELHARDOLEATOCATCHISSUSESUSEUSENINCIENVIRENTMENTS.3)schictcross.3)schoscross.3)

Java編譯器(Javac)在實現平台獨立性中的作用是什麼?Java編譯器(Javac)在實現平台獨立性中的作用是什麼?May 01, 2025 am 12:06 AM

Java編譯器通過將源代碼轉換為平台無關的字節碼,實現了Java的平台獨立性,使得Java程序可以在任何安裝了JVM的操作系統上運行。

在平台獨立性的平台獨立性上使用字節碼優於本機代碼的優點是什麼?在平台獨立性的平台獨立性上使用字節碼優於本機代碼的優點是什麼?Apr 30, 2025 am 12:24 AM

ByteCodeachievesPlatFormIndenceByByByByByByExecutedBoviratualMachine(VM),允許CodetorunonanyplatformwithTheApprepreprepvm.Forexample,Javabytecodecodecodecodecanrunonanydevicewithajvm

Java真的100%獨立於平台嗎?為什麼或為什麼不呢?Java真的100%獨立於平台嗎?為什麼或為什麼不呢?Apr 30, 2025 am 12:18 AM

Java不能做到100%的平台獨立性,但其平台獨立性通過JVM和字節碼實現,確保代碼在不同平台上運行。具體實現包括:1.編譯成字節碼;2.JVM的解釋執行;3.標準庫的一致性。然而,JVM實現差異、操作系統和硬件差異以及第三方庫的兼容性可能影響其平台獨立性。

Java的平台獨立性如何支持代碼可維護性?Java的平台獨立性如何支持代碼可維護性?Apr 30, 2025 am 12:15 AM

Java通過“一次編寫,到處運行”實現平台獨立性,提升代碼可維護性:1.代碼重用性高,減少重複開發;2.維護成本低,只需一處修改;3.團隊協作效率高,方便知識共享。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

MantisBT

MantisBT

Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

mPDF

mPDF

mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),

WebStorm Mac版

WebStorm Mac版

好用的JavaScript開發工具

VSCode Windows 64位元 下載

VSCode Windows 64位元 下載

微軟推出的免費、功能強大的一款IDE編輯器