簡體   English   中英

Java中的動態多態和static多態有什么區別?

[英]What is the difference between dynamic and static polymorphism in Java?

誰能提供一個簡單的例子來解釋 Java 中動態Static多態性之間的區別?

多態性

1.靜態綁定/編譯時綁定/早期綁定/方法重載。(在同一個類中)

2. 動態綁定/運行時綁定/后期綁定/方法覆蓋。(在不同的類中)

重載示例:

class Calculation {  
  void sum(int a,int b){System.out.println(a+b);}  
  void sum(int a,int b,int c){System.out.println(a+b+c);}  

  public static void main(String args[]) {  
    Calculation obj=new Calculation();  
    obj.sum(10,10,10);  // 30
    obj.sum(20,20);     //40 
  }  
}  

覆蓋示例:

class Animal {    
   public void move(){
      System.out.println("Animals can move");
   }
}

class Dog extends Animal {

   public void move() {
      System.out.println("Dogs can walk and run");
   }
}

public class TestDog {

   public static void main(String args[]) {
      Animal a = new Animal(); // Animal reference and object
      Animal b = new Dog(); // Animal reference but Dog object

      a.move();//output: Animals can move

      b.move();//output:Dogs can walk and run
   }
}
  • 方法重載將是靜態多態的一個例子

  • 而覆蓋將是動態多態性的一個例子。

    因為,在重載的情況下,編譯器在編譯時知道將哪個方法鏈接到調用。 但是,它是在運行時確定的動態多態性

動態(運行時)多態運行時存在的多態性。 在這里,Java 編譯器不知道在編譯時調用了哪個方法。 只有 JVM 決定在運行時調用哪個方法。 方法重載和使用實例方法的方法覆蓋是動態多態的例子。

例如,

  • 考慮一個序列化和反序列化不同類型文檔的應用程序。

  • 我們可以將“文檔”作為基類,並從它派生出不同的文檔類型類。 例如 XMLDocument 、 WordDocument 等。

  • 文檔類將' Serialize() '和' De-serialize() '方法定義為虛擬方法,每個派生類將根據文檔的實際內容以自己的方式實現這些方法。

  • 當需要對不同類型的文檔進行序列化/反序列化時,文檔對象將通過 'Document' 類引用(或指針)引用,並且當調用 'Serialize()' 或 'De-serialize()' 方法時在它上面,調用適當版本的虛擬方法。

靜態(編譯時)多態性是在編譯時表現出的多態性。 在這里,Java 編譯器知道調用了哪個方法。 使用靜態方法的方法重載和方法覆蓋; 使用私有或最終方法覆蓋的方法是靜態多態的例子

例如,

  • 一個員工對象可能有兩種 print() 方法,一種不帶參數,另一種帶有前綴字符串,與員工數據一起顯示。

  • 鑒於這些接口,當 print() 方法在沒有任何參數的情況下被調用時,編譯器查看函數參數就知道要調用哪個函數並相應地生成目標代碼。

有關更多詳細信息,請閱讀“什么是多態性”(Google it)。

綁定是指方法調用和方法定義之間的鏈接。

這張圖片清楚地顯示了什么是綁定。

綁定

在這張圖中,“a1.methodOne()”調用綁定了對應的methodOne()定義,“a1.methodTwo()”調用綁定了對應的methodTwo()定義。

對於每個方法調用,都應該有正確的方法定義。 這是java中的規則。 如果編譯器沒有看到每個方法調用的正確方法定義,它就會拋出錯誤。

現在,來談談java中的靜態綁定和動態綁定。

Java中的靜態綁定:

靜態綁定是在編譯期間發生的綁定。 它也稱為早期綁定,因為綁定發生在程序實際運行之前

.

靜態綁定可以如下圖所示進行演示。

在此處輸入圖片說明

在這張圖中,'a1'是A類引用變量,指向A類對象。'a2'也是A類引用變量,但指向B類對象。

在編譯期間,在綁定時,編譯器不會檢查特定引用變量指向的對象類型。 它只是檢查調用方法的引用變量的類型,並檢查該類型中是否存在針對它的方法定義。

例如,對於上圖中的“a1.method()”方法調用,編譯器會檢查A類中是否存在method()的方法定義。因為'a1'是A類類型。 同樣,對於“a2.method()”方法調用,它會檢查A類中是否存在method()的方法定義。因為'a2'也是A類類型。 它不檢查“a1”和“a2”指向哪個對象。 這種類型的綁定稱為靜態綁定。

Java 中的動態綁定:

動態綁定是在運行時發生的綁定。 它也稱為后期綁定,因為綁定發生在程序實際運行時。

在運行時實際對象用於綁定。 例如,對於上圖中的“a1.method()”調用,將調用“a1”所指向的實際對象的method()。 對於“a2.method()”調用,將調用“a2”指向的實際對象的method()。 這種類型的綁定稱為動態綁定。

上面例子的動態綁定可以如下所示。

在此處輸入圖片說明

參考static-binding-and-dynamic-binding-in-java

簡單來說:

靜態多態性:相同的方法名稱在相同的類(不同的簽名)中使用不同類型或數量的參數進行重載 目標方法調用在編譯時解決。

動態多態:在不同的類中用相同的簽名覆蓋相同的方法。 被調用方法的對象類型在編譯時是未知的,但將在運行時決定。

通常重載不會被視為多態。

從java教程頁面

一個類的子類可以定義它們自己獨特的行為,同時共享父類的一些相同功能

方法重載是編譯時/靜態多態的一個例子,因為方法調用和方法定義之間的方法綁定發生在編譯時,它取決於類的引用(在編譯時創建的引用並進入堆棧)。

方法覆蓋是運行時/動態多態的一個例子,因為方法調用和方法定義之間的方法綁定發生在運行時,它取決於類的對象(在運行時創建的對象並進入堆)。

多態性:多態性是對象具有多種形式的能力。 當使用父類引用來引用子類對象時,OOP 中多態性的最常見用途發生。

動態綁定/運行時多態:

運行時多態也稱為方法覆蓋。 在此機制中,在運行時解析對重寫函數的調用。

public class DynamicBindingTest {

    public static void main(String args[]) {
        Vehicle vehicle = new Car(); //here Type is vehicle but object will be Car
        vehicle.start();       //Car's start called because start() is overridden method
    }
}

class Vehicle {

    public void start() {
        System.out.println("Inside start method of Vehicle");
    }
}

class Car extends Vehicle {

    @Override
    public void start() {
        System.out.println("Inside start method of Car");
    }
}

輸出:

汽車內部啟動方法

靜態綁定/編譯時多態:

調用哪個方法僅在編譯時決定。

public class StaticBindingTest {

    public static void main(String args[])  {
       Collection c = new HashSet();
       StaticBindingTest et = new StaticBindingTest();
       et.sort(c);

    }

    //overloaded method takes Collection argument
    public Collection sort(Collection c){
        System.out.println("Inside Collection sort method");
        return c;
    }


   //another overloaded method which takes HashSet argument which is sub class
    public Collection sort(HashSet hs){
        System.out.println("Inside HashSet sort method");
        return hs;
    }

}

輸出:內部集合排序方法

方法重載稱為靜態多態,也稱為編譯時多態靜態綁定,因為重載的方法調用在編譯時由編譯器根據參數列表和我們調用方法的引用來解決。

方法覆蓋被稱為動態多態性或簡單多態性運行時方法調度動態綁定,因為覆蓋的方法調用在運行時得到解決。

為了理解為什么會這樣,讓我們​​舉一個MammalHuman類的例子

class Mammal {
    public void speak() { System.out.println("ohlllalalalalalaoaoaoa"); }
}

class Human extends Mammal {

    @Override
    public void speak() { System.out.println("Hello"); }

    public void speak(String language) {
        if (language.equals("Hindi")) System.out.println("Namaste");
        else System.out.println("Hello");
    }

}

我在下面的代碼行中包含了輸出和字節碼

Mammal anyMammal = new Mammal();
anyMammal.speak();  // Output - ohlllalalalalalaoaoaoa
// 10: invokevirtual #4 // Method org/programming/mitra/exercises/OverridingInternalExample$Mammal.speak:()V

Mammal humanMammal = new Human();
humanMammal.speak(); // Output - Hello
// 23: invokevirtual #4 // Method org/programming/mitra/exercises/OverridingInternalExample$Mammal.speak:()V

Human human = new Human();
human.speak(); // Output - Hello
// 36: invokevirtual #7 // Method org/programming/mitra/exercises/OverridingInternalExample$Human.speak:()V

human.speak("Hindi"); // Output - Namaste
// 42: invokevirtual #9 // Method org/programming/mitra/exercises/OverridingInternalExample$Human.speak:(Ljava/lang/String;)V

通過查看上面的代碼,我們可以看到 humanMammal.speak() 、 human.speak() 和 human.speak("Hindi") 的字節碼完全不同,因為編譯器能夠根據參數列表區分它們和類參考。 這就是方法重載被稱為靜態多態的原因

但是 anyMammal.speak() 和 humanMammal.speak() 的字節碼是相同的,因為根據編譯器,這兩種方法都是在 Mammal 引用上調用的,但是兩種方法調用的輸出是不同的,因為在運行時 JVM 知道引用所持有的對象和 JVM 調用對象上的方法,這就是方法覆蓋被稱為動態多態的原因。

所以從上面的代碼和字節碼可以看出,在編譯階段調用方法是從引用類型考慮的。 但是在執行時方法將從引用持有的對象中調用。

如果您想了解更多相關信息,您可以閱讀更多關於JVM 如何內部處理方法重載和覆蓋

靜態多態性:是在編譯期間確定要完成的方法的決定。 方法重載就是一個例子。

動態多態:是在運行時設置選擇執行哪個方法的決定。 方法覆蓋可能就是一個例子。

多態性是指對象對於同一個觸發器表現出不同行為的能力。

靜態多態(Compile-time Polymorphism)

  • 靜態多態決定在編譯時執行哪個方法。
  • 方法重載是靜態多態的一個例子,它需要發生靜態多態。
  • 靜態多態是通過靜態綁定實現的。
  • 靜態多態發生在同一個類中。
  • 靜態多態不需要對象賦值。
  • 靜態多態不涉及繼承。

動態多態(Runtime Polymorphism)

  • 動態多態決定在運行時執行哪個方法。
  • 方法覆蓋是動態多態的一個例子,它需要發生動態多態。
  • 動態多態是通過動態綁定實現的。
  • 動態多態發生在不同的類之間。
  • 將子類對象分配給超類對象以實現動態多態性是必需的。
  • 涉及動態多態性的繼承。

編譯時多態性(靜態綁定/早期綁定):在靜態多態性中,如果我們在代碼中調用一個方法,那么實際調用該方法的哪個定義僅在編譯時解析。

(或)

在編譯時,Java 通過檢查方法簽名知道要調用哪個方法。 因此,這稱為編譯時多態性或靜態綁定。

動態多態性(后期綁定/運行時多態性):在運行時,Java 會等到運行時來確定引用實際指向的是哪個對象。 方法解析是在運行時進行的,因為我們稱之為運行時多態。

考慮下面的代碼:

public class X
{
    public void methodA() // Base class method
    {
        System.out.println ("hello, I'm methodA of class X");
    }
}

public class Y extends X
{
    public void methodA() // Derived Class method
    {
        System.out.println ("hello, I'm methodA of class Y");
    }
}
public class Z
{
public static void main (String args []) {

    //this takes input from the user during runtime
    System.out.println("Enter x or y");
    Scanner scanner = new Scanner(System.in);
    String value= scanner.nextLine();

    X obj1 = null;
    if(value.equals("x"))
        obj1 = new X(); // Reference and object X
    else if(value.equals("y"))
        obj2 = new Y(); // X reference but Y object
    else
        System.out.println("Invalid param value");

    obj1.methodA();
}
}

現在,查看代碼,您永遠無法判斷將執行 methodA() 的哪個實現,因為這取決於用戶在運行時給出的值。 因此,只有在運行時才決定調用哪個方法。 因此,運行時多態性。

方法重載是一種編譯時多態,我們舉個例子來理解這個概念。

class Person                                            //person.java file
{
    public static void main ( String[] args )
    {
      Eat e = new Eat();
       e.eat(noodle);                                //line 6
    }

   void eat (Noodles n)      //Noodles is a object    line 8                     
   {

   }
   void eat ( Pizza p)           //Pizza is a object
  {

  }

}

在這個例子中,Person 有一個 eat 方法,表示他可以吃 Pizza 或 Noodles。 當我們編譯這個 Person.java 時,eat 方法被重載,編譯器解析方法調用“e.eat(noodles) [在第 6 行],使用第 8 行中指定的方法定義,即它以面條作為參數的方法而整個過程是由Compiler完成的,所以是Compile time Polymorphism,將方法調用替換為方法定義的過程稱為綁定,在這種情況下,是由編譯器完成的,所以稱為早期綁定。

繼 Naresh 的回答之后,動態多態性在 Java 中只是“動態的”,因為虛擬機的存在及其在運行時解釋代碼而不是本機運行的代碼的能力。

在 C++ 中,如果使用 gcc 將其編譯為本地二進制文件,則必須在編譯時解析它,顯然; 然而,虛擬表中的運行時跳轉和 thunk 仍然被稱為“查找”或“動態”。 如果 C 繼承 B,並且您聲明B* b = new C(); b->method1(); B* b = new C(); b->method1(); , b 會被編譯器解析為指向 C 內部的一個 B 對象(對於簡單的類繼承一個類的情況,C 和 C 內部的 B 對象會從相同的內存地址開始,所以什么都不用做;它會指向他們都使用的 vptr)。 如果 C 繼承了 B 和 A,則 method1 的 C 條目內的 A 對象的虛函數表將有一個 thunk,它將指針偏移到封裝 C 對象的開頭,然后將其傳遞給真正的 A::method1()在 C 已覆蓋的文本段中。 對於C* c = new C(); c->method1() C* c = new C(); c->method1() ,c 將已經指向外部 C 對象並且指針將被傳遞給文本段中的 C::method1() 。 參考: http : //www.programmersought.com/article/2572545946/

在java中,對於B b = new C(); b.method1(); B b = new C(); b.method1(); ,虛擬機能夠動態檢查與 b 配對的對象的類型,並可以傳遞正確的指針並調用正確的方法。 虛擬機的額外步驟消除了對虛擬函數表或編譯時解析的類型的需要,即使在編譯時可以知道。 這只是一種不同的方式,當涉及虛擬機並且代碼僅編譯為字節碼時才有意義。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM