Java プログラムの実行には、コンパイルと実行 (解釈) の 2 つのステップが必要です。同時に、Java はオブジェクト指向プログラミング言語です。サブクラスと親クラスが同じメソッドを持ち、サブクラスが親クラスのメソッドをオーバーライドする場合、プログラムが実行時にメソッドを呼び出すとき、親クラスのメソッドを呼び出す必要がありますか、それともサブクラスのオーバーライドされたメソッドを呼び出す必要がありますか? Java を初めて学習するときに遭遇する問題は次のとおりです。ここではまず、どのメソッドを呼び出すか、または変数の操作をバインディングと呼ぶかを決定します。
Java には 2 つのバインディング メソッドがあり、1 つは静的バインディングであり、早期バインディングとも呼ばれます。もう 1 つは動的バインディングであり、遅延バインディングとも呼ばれます。
違いの比較
1. 静的バインディングはコンパイル時に発生し、動的バインディングは実行時に発生します
2. private、static、またはfinalで変更された変数またはメソッドには静的バインディングを使用します。仮想メソッド (サブクラスによってオーバーライドできるメソッド) は、ランタイム オブジェクトに基づいて動的にバインドされます。
3. 静的バインディングはクラス情報を使用して完了しますが、動的バインディングはオブジェクト情報を使用して完了する必要があります。
4. オーバーロードされたメソッドは静的バインディングを使用して完成され、オーバーライド メソッドは動的バインディングを使用して完成されます。
オーバーロードされたメソッドの例
オーバーロードされたメソッドの例を次に示します。
public class TestMain { public static void main(String[] args) { String str = new String(); Caller caller = new Caller(); caller.call(str); } static class Caller { public void call(Object obj) { System.out.println("an Object instance in Caller"); } public void call(String str) { System.out.println("a String instance in in Caller"); } } }
の実行結果は
22:19 $ java TestMain a String instance in in Caller
です。 上記のコードには、call メソッドの 2 つのオーバーロードされた実装があり、1 つは Object 型のオブジェクトをパラメーターとして受け取り、もう 1 つは String 型のオブジェクトをパラメーターとして受け取ります。 str は String オブジェクトであり、String 型パラメータを受け取るすべての call メソッドが呼び出されます。ここでのバインディングは、コンパイル時のパラメーターの型に基づく静的バインディングです。
検証
外観を見るだけでは静的にバインドされていることを証明することはできません。javap を使用してコンパイルすることで検証できます。
22:19 $ javap -c TestMain Compiled from "TestMain.java" public class TestMain { public TestMain(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: new #2 // class java/lang/String 3: dup 4: invokespecial #3 // Method java/lang/String."<init>":()V 7: astore_1 8: new #4 // class TestMain$Caller 11: dup 12: invokespecial #5 // Method TestMain$Caller."<init>":()V 15: astore_2 16: aload_2 17: aload_1 18: invokevirtual #6 // Method TestMain$Caller.call:(Ljava/lang/String;)V 21: return }
はこの行 18 を確認しました: invokevirtual #6 // Method TestMain$Caller.call:(Ljava/lang/String;)V は実際に静的にバインドされており、 String オブジェクトをパラメーターとして受け取る呼び出し側メソッドが呼び出されていることが確認されています。
メソッドのオーバーライド例
public class TestMain { public static void main(String[] args) { String str = new String(); Caller caller = new SubCaller(); caller.call(str); } static class Caller { public void call(String str) { System.out.println("a String instance in Caller"); } } static class SubCaller extends Caller { @Override public void call(String str) { System.out.println("a String instance in SubCaller"); } } }
実行結果は
22:27 $ java TestMain a String instance in SubCaller
上記のコードでは、Callerにcallメソッドの実装があり、SubCallerはCallerを継承してcallメソッドの実装を書き換えています。 Caller 型の変数 callerSub を宣言しましたが、この変数は SubCaller オブジェクトを指します。結果によれば、Callerのcallメソッドではなく、SubCallerのcallメソッド実装を呼び出していることがわかります。この結果が生じる理由は、動的バインディングが実行時に発生し、バインド プロセス中にどのバージョンの呼び出しメソッド実装を呼び出すかを決定する必要があるためです。
検証
動的バインディングはjavapを使用して直接検証することはできず、静的バインディングが実行されていないことが証明された場合、動的バインディングが実行されていることを意味します。
22:27 $ javap -c TestMain Compiled from "TestMain.java" public class TestMain { public TestMain(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: new #2 // class java/lang/String 3: dup 4: invokespecial #3 // Method java/lang/String."<init>":()V 7: astore_1 8: new #4 // class TestMain$SubCaller 11: dup 12: invokespecial #5 // Method TestMain$SubCaller."<init>":()V 15: astore_2 16: aload_2 17: aload_1 18: invokevirtual #6 // Method TestMain$Caller.call:(Ljava/lang/String;)V 21: return }
上記の結果として、 18: invokevirtual #6 // Method TestMain$Caller.call:(Ljava/lang/String;)V これは、TestMain$SubCaller.call ではなく TestMain$Caller.call です。コンパイル時に使用されます。サブクラスの実装を呼び出すか、親クラスの実装を呼び出すかを決定します。これにより、実行時の動的バインディングのみに任せることができます。
オーバーロードとオーバーライドの場合
次の例は少し異常です。Caller クラスには call メソッドの 2 つのオーバーロードがあり、さらに複雑なのは、SubCaller が Caller を統合し、これら 2 つのメソッドを書き換えることです。実際、この状況は上記 2 つの状況が複合した状況です。
次のコードは、最初に静的にバインドされて、パラメーターが String オブジェクトである呼び出しメソッドを決定し、次に実行時に動的にバインドされて、サブクラスまたは親クラスの呼び出し実装を実行するかどうかを決定します。
public class TestMain { public static void main(String[] args) { String str = new String(); Caller callerSub = new SubCaller(); callerSub.call(str); } static class Caller { public void call(Object obj) { System.out.println("an Object instance in Caller"); } public void call(String str) { System.out.println("a String instance in in Caller"); } } static class SubCaller extends Caller { @Override public void call(Object obj) { System.out.println("an Object instance in SubCaller"); } @Override public void call(String str) { System.out.println("a String instance in in SubCaller"); } } }
実行結果は
22:30 $ java TestMain a String instance in in SubCaller
検証
上で紹介したのでここでは逆コンパイル結果のみ載せておきます
22:30 $ javap -c TestMain Compiled from "TestMain.java" public class TestMain { public TestMain(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: new #2 // class java/lang/String 3: dup 4: invokespecial #3 // Method java/lang/String."<init>":()V 7: astore_1 8: new #4 // class TestMain$SubCaller 11: dup 12: invokespecial #5 // Method TestMain$SubCaller."<init>":()V 15: astore_2 16: aload_2 17: aload_1 18: invokevirtual #6 // Method TestMain$Caller.call:(Ljava/lang/String;)V 21: return }
気になる質問
動的バインディングしないと無理なのでしょうか?
実際、理論的には、特定のメソッドのバインドは静的バインディングによっても実現できます。例:
public static void main(String[] args) { String str = new String(); final Caller callerSub = new SubCaller(); callerSub.call(str); }
たとえば、ここでは callerSub は subCaller のオブジェクトを保持しており、callerSub 変数は Final であり、call メソッドはすぐに実行されます。理論的には、コンパイラーは十分な分析によって SubCaller の call メソッドを呼び出す必要があることを知ることができます。コードの。
しかし、なぜ静的バインディングがないのでしょうか?
Caller が call メソッドを実装する特定のフレームワークの BaseCaller クラスから継承し、BaseCaller が SuperCaller から継承すると仮定します。呼び出しメソッドも SuperCaller に実装されています。
特定のフレームワーク 1.0 で BaseCaller と SuperCaller を想定します
static class SuperCaller { public void call(Object obj) { System.out.println("an Object instance in SuperCaller"); } } static class BaseCaller extends SuperCaller { public void call(Object obj) { System.out.println("an Object instance in BaseCaller"); } }
そして、そのような実装を実装するためにフレームワーク 1.0 を使用します。 Caller は BaseCaller を継承し、super.call メソッドを呼び出します。
public class TestMain { public static void main(String[] args) { Object obj = new Object(); SuperCaller callerSub = new SubCaller(); callerSub.call(obj); } static class Caller extends BaseCaller{ public void call(Object obj) { System.out.println("an Object instance in Caller"); super.call(obj); } public void call(String str) { System.out.println("a String instance in in Caller"); } } static class SubCaller extends Caller { @Override public void call(Object obj) { System.out.println("an Object instance in SubCaller"); } @Override public void call(String str) { System.out.println("a String instance in in SubCaller"); } } }
次に、静的バインディングによって上記の Caller の super.call が BaseCaller.call として実装されると判断できることを前提として、このフレームワークのバージョン 1.0 に基づいてクラス ファイルをコンパイルしました。
次に、このフレームワークのバージョン 1.1 では BaseCaller が SuperCaller の呼び出しメソッドを書き換えないと再び仮定します。次に、バージョン 1.1 では super.call が存在するため、静的にバインドできる呼び出し実装がバージョン 1.1 で問題を引き起こすという上記の仮定が適用されます。 SuperCall の呼び出しメソッド実装は、静的バインディングによって決定されると想定される BaseCaller の呼び出しメソッド実装ではありません。
そのため、実際には静的にバインドできる一部のものは、安全性と一貫性を考慮して、単純に動的にバインドされます。
最適化に関するインスピレーションはありましたか?
動的バインディングでは、実行時に実行するメソッド実装または変数のバージョンを決定する必要があるため、静的バインディングよりも時間がかかります。
そのため、全体的な設計に影響を与えることなく、メソッドや変数を private、static、final で変更することを検討できます。
Java の静的バインディングと動的バインディングの詳細については、PHP 中国語 Web サイトに注目してください。