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