静的対 Javaでの動的バインディング


103

現在、自分のクラスの1つに割り当てを行っています。その中で、Java構文を使用して、静的および動的バインディングの例を示す必要があります

静的バインディングはコンパイル時に発生し、動的バインディングは実行時に発生するという基本的な概念を理解していますが、それらが実際にどのように機能するか理解できません。

この例を示す静的バインディングの例をオンラインで見つけました:

public static void callEat(Animal animal) {
    System.out.println("Animal is eating");
}

public static void callEat(Dog dog) {
    System.out.println("Dog is eating");
}

public static void main(String args[])
{
    Animal a = new Dog();
    callEat(a);
}

そして、これは静的バインディングcallEat使用するため「動物が食べている」と出力しますこれが静的バインディングと見なされる理由についてはわかりません。

これまでのところ、私が見た情報源のどれも、これを私が理解できる方法で説明できていません。



1
「バインディング」と呼ばれるいくつかの異なる概念があることに注意してください。この特定のケースでは、このタイプのバインディング(類似しているが同一ではないメソッド「シグネチャ」の選択を含む)の場合、コンパイラは(実行時に変化しないため「静的」な決定を行います)、パラメータが「動物」は変数「a」の(静的)型であるためです。
Hot Licks 2013

(特定のメソッドシグネチャの選択がランタイムまで残され、callEat(Dog)が選択される言語もあります。)
Hot Licks

回答:


114

Javarevisitedブログの投稿から:

静的バインディングと動的バインディングのいくつかの重要な違いは次のとおりです。

  1. Javaの静的バインディングはコンパイル時に発生し、動的バインディングは実行時に発生します。
  2. privatefinalおよびstaticメソッドと変数は静的バインディングを使用し、コンパイラーによって結合されますが、仮想メソッドはランタイムオブジェクトに基づいて実行時に結合されます。
  3. 静的バインディングはバインディングにTypeclassJavaで)情報を使用し、動的バインディングはオブジェクトを使用してバインディングを解決します。
  4. オーバーロードされたメソッドは静的バインディングを使用して結合され、オーバーライドされたメソッドは実行時に動的バインディングを使用して結合されます。

以下は、Javaの静的バインディングと動的バインディングの両方を理解するのに役立つ例です。

Javaでの静的バインディングの例

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;
    }
}

出力:コレクション内ソートメソッド

Javaでの動的バインディングの例

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");
    }
}

出力:車の内部開始メソッド



11
私はまだ違いを理解していません
テクナジ

9
@technaziスタティックバインディングは、型(たとえば、コレクションc = new HashSet();の前は何であるか)を調べるだけなので、ハッシュセットである場合、コレクションオブジェクトとしてのみ表示されます。動的バインディングでは、実際のオブジェクトが考慮されます(equalsの後にあるため、実際にHashSetを認識します)。
マーク

22

メソッド呼び出しをメソッド本体に接続することは、バインディングと呼ばれます。Maulikが言ったように、「静的バインディングはバインディングにType(Class in Java)情報を使用し、動的バインディングはObjectを使用してバインディングを解決します。」したがって、このコード:

public class Animal {
    void eat() {
        System.out.println("animal is eating...");
    }
}

class Dog extends Animal {

    public static void main(String args[]) {
        Animal a = new Dog();
        a.eat(); // prints >> dog is eating...
    }

    @Override
    void eat() {
        System.out.println("dog is eating...");
    }
}

結果は次のようになります。犬が食べている...使用するメソッドを見つけるためにオブジェクト参照を使用しているためです。上記のコードを次のように変更すると、

class Animal {
    static void eat() {
        System.out.println("animal is eating...");
    }
}

class Dog extends Animal {

    public static void main(String args[]) {

        Animal a = new Dog();
        a.eat(); // prints >> animal is eating...

    }

    static void eat() {
        System.out.println("dog is eating...");
    }
}

それは生成します:動物は食べています...それは静的メソッドなので、どの静的メソッドを呼び出すかを解決するためにタイプ(この場合は動物)を使用しています。静的メソッドのほかに、プライベートメソッドとファイナルメソッドは同じアプローチを使用します。


1
それaが実際Dogにコンパイル時であるとJavaが推測できないのはなぜですか?
MinhNghĩa

4

コンパイラはAnimal、「a」のタイプがであることのみを認識します。これはコンパイル時に発生するため、静的バインディング(メソッドのオーバーロード)と呼ばれます。ただし、動的バインディングの場合は、Dogクラスメソッドを呼び出します。以下は動的バインディングの例です。

public class DynamicBindingTest {

    public static void main(String args[]) {
        Animal a= new Dog(); //here Type is Animal but object will be Dog
        a.eat();       //Dog's eat called because eat() is overridden method
    }
}

class Animal {

    public void eat() {
        System.out.println("Inside eat method of Animal");
    }
}

class Dog extends Animal {

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

出力:Dogの内部のeatメソッド


これは、「静的コンテキストから非静的クラス/メソッドを参照できない」などのコンパイルエラーをスローしませんか?mainは静的であることを念頭に置いて、常に混乱しています。前もって感謝します。
Amnor 16

3

静的および動的バインディングが実際にどのように機能するかを理解するために、またはそれらはコンパイラとJVMによってどのように識別されますか?

以下にMammal、メソッドspeak()Humanクラスを持つ親クラスがあり、クラスが拡張されMammalspeak()メソッドがオーバーライドされ、再度でオーバーロードされる例を見てみましょうspeak(String language)

public class OverridingInternalExample {

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

    private static class Human extends Mammal {

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

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

        @Override
        public String toString() { return "Human Class"; }

    }

    //  Code below contains the output and bytecode of the method calls
    public static void main(String[] args) {
        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
    }
}

上記のコードをコンパイルしjavap -verbose OverridingInternalExample、を使用してバイトコードを確認しようとすると、コンパイラーが定数テーブルを生成することがわかります。ここで、すべてのメソッド呼び出しに整数コードを割り当て、プログラム自体のバイトコード(プログラム自体に含まれている)を割り当てます(すべてのメソッド呼び出しの下のコメントを参照してください)

プログラムバイトコード

上記のコードを見て、私たちはのバイトコードことを確認することができhumanMammal.speak()human.speak()かつhuman.speak("Hindi")全く異なっている(invokevirtual #4invokevirtual #7invokevirtual #9)コンパイラは引数リストとクラス参照に基づいて、それらを区別することができますので。これらはすべてコンパイル時に静的に解決されるため、メソッドのオーバーロード静的ポリモーフィズムまたは静的バインディングとして知られています。

しかし、コンパイラーによると両方のメソッドが参照で呼び出されるため、anyMammal.speak()とのバイトコードhumanMammal.speak()は同じです(invokevirtual #4Mammal

では、両方のメソッド呼び出しのバイトコードが同じである場合、JVMはどのメソッドを呼び出すかをどのようにして知るのでしょうか。

まあ、答えはバイトコード自体に隠されており、それはinvokevirtual命令セットです。JVMは、このinvokevirtual命令を使用して、C ++仮想メソッドに相当するJavaを呼び出します。C ++では、別のクラスの1つのメソッドをオーバーライドする場合は、それを仮想として宣言する必要がありますが、Javaでは、子クラスのすべてのメソッドをオーバーライドできるため、Javaではすべてのメソッドがデフォルトで仮想です(private、final、staticメソッドを除く)。

Javaでは、すべての参照変数が2つの隠しポインタを保持します

  1. オブジェクトのメソッドを再び保持するテーブルへのポインターとClassオブジェクトへのポインター。例[speak()、speak(String)Classオブジェクト]
  2. インスタンス変数の値など、そのオブジェクトのデータ用にヒープに割り当てられたメモリへのポインター。

したがって、すべてのオブジェクト参照は、そのオブジェクトのすべてのメソッド参照を保持するテーブルへの参照を間接的に保持します。JavaはC ++からこの概念を借用しており、このテーブルは仮想テーブル(vtable)として知られています。

vtableは、仮想メソッド名とそれらの配列インデックスへの参照を保持する構造体のような配列です。JVMは、クラスをメモリにロードするときに、クラスごとに1つのvtableのみを作成します。

したがって、JVMがinvokevirtual命令セットに遭遇するたびに、そのクラスのvtableでメソッド参照をチェックし、特定のメソッド(この場合は参照ではなくオブジェクトからのメソッド)を呼び出します。

これらすべてが実行時にのみ解決され、実行時にJVMが呼び出すメソッドを認識できるため、メソッドのオーバーライド動的ポリモーフィズムまたは単にポリモーフィズムまたはダイナミックバインディングと呼ばれるのはこのためです。

詳細については、私の記事「JVMによるメソッドのオーバーロードとオーバーライドの内部処理」を参照してください。


2

コンパイラーの設計中の静的バインディングと動的バインディングには、3つの大きな違いがあります。また、変数プロシージャーランタイム環境に転送される方法異なります。これらの違いは次のとおりです。

静的バインディング静的バインディングでは、次の3つの問題について説明します。

  • 手順の定義

  • 名前の宣言(変数など)

  • 宣言の範囲

動的バインディング動的バインディングで遭遇する3つの問題は次のとおりです。

  • プロシージャのアクティブ化

  • 名前のバインド

  • バインディングの寿命


1

親クラスと子クラスの静的メソッド:静的バインディング

public class test1 {   
    public static void main(String args[]) {
        parent pc = new child(); 
        pc.start(); 
    }
}

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

class child extends parent {

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

// Output => Inside start method of parent

動的バインディング:

public class test1 {   
    public static void main(String args[]) {
        parent pc = new child();
        pc.start(); 
    }
}

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

class child extends parent {

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

// Output => Inside start method of child

0

ここでのすべての答えは正しいですが、足りないものを追加したいと思います。静的メソッドをオーバーライドしている場合、オーバーライドしているように見えますが、実際にはメソッドオーバーライドではありません。代わりに、メソッド非表示と呼ばれます。Javaでは静的メソッドオーバーライドできません

以下の例を見てください:

class Animal {
    static void eat() {
        System.out.println("animal is eating...");
    }
}

class Dog extends Animal {

    public static void main(String args[]) {

        Animal a = new Dog();
        a.eat(); // prints >> animal is eating...

    }

    static void eat() {
        System.out.println("dog is eating...");
    }
}

動的バインディングでは、参照変数が保持しているオブジェクトのタイプではなく、参照のタイプに応じてメソッドが呼び出されます。メソッドの非表示は動的なポリモーフィズムではないため、静的バインディングが発生します。eat()の前にある静的キーワードを削除し、それを非静的メソッドにすると、動的な多態性が表示され、メソッドを非表示にすることはありません。

私は私の答えをサポートするために以下のリンクを見つけました:https : //youtu.be/tNgZpn7AeP0


0

静的バインディングタイプのオブジェクトの場合はコンパイル時に決定されますが、動的バインディングタイプの場合はオブジェクトの実行時に決定されます。



class Dainamic{

    void run2(){
        System.out.println("dainamic_binding");
    }

}


public class StaticDainamicBinding extends Dainamic {

    void run(){
        System.out.println("static_binding");
    }

    @Override
    void run2() {
        super.run2();
    }

    public static void main(String[] args) {
        StaticDainamicBinding st_vs_dai = new StaticDainamicBinding();
        st_vs_dai.run();
        st_vs_dai.run2();
    }

}

-3

コンパイラはコンパイル時にバインディングを知っているからです。たとえば、インターフェイスでメソッドを呼び出す場合、コンパイラーはそれを知ることができず、メソッドが呼び出された実際のオブジェクトは複数ある可能性があるため、実行時にバインディングが解決されます。したがって、これはランタイムバインディングまたは動的バインディングです。

タイプを指定したため、呼び出しはコンパイル時にAnimalクラスにバインドされます。その変数をどこか別のメソッドに渡した場合、実際にどのクラスになるかは(あなたが書いたのであなたを除いて)誰にもわかりません。唯一の手がかりは、宣言された種類の動物です。


1
違います。インターフェースで呼び出しを行っている場合、コンパイラーはまったく同じ決定を行います。
Hot Licks 2013

@HotLicks何と同じ決定ですか?クラスをコンパイルしてインターフェースでfoo(String str)メソッドを呼び出す場合、コンパイラーはコンパイル時にfoo(String str)メソッドが呼び出されるクラスを知ることができません。実行時にのみ、メソッドの呼び出しを特定のクラス実装にバインドできます。
アーロン

ただし、特定のメソッドシグネチャへの静的バインディングは引き続き発生します。コンパイラーは、callEat(Dog)よりもcallEat(Animal)を選択します。
Hot Licks 2013

@HotLicksもちろん、それは私が答えた質問ではありません。多分それは私を誤解させたかもしれません:DIはそれをインターフェイスで呼び出すのと比較して、コンパイル時にコンパイラが実際に異なるサブクラス/実装をインスタンス化したかどうかを知ることができないことを強調しました。
アーロン

実際、コンパイル時に、コンパイラは(この場合)「a」が犬であることを非常に簡単に知ることができます。実際、それを「忘れる」にはある程度の時間がかかる可能性があります。
Hot Licks 2013
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.