問題の説明
静的メソッドをJavaで書き直せないのはなぜですか?静的メソッドがインスタンスメソッドを非表示にできないのはなぜですか?等々。
準備
まず、書き換えの意味を理解します。書き換えは、サブクラスの親クラスのインスタンスメソッドの再定義関数であり、戻り値の型、メソッド名、パラメータリストは一貫しており、書き換えメソッドの呼び出しは主に実際のメソッドに依存します。タイプ。実際の型がこのメソッドを実装している場合は、このメソッドを直接呼び出します。実装されていない場合は、継承関係の低いものから高いものへと実装を検索します。次に、問題が再び発生します。インスタンスメソッドに対してのみオーバーライドできるのはなぜですか。私はめまいがするので、これら2つの問題はここでお互いの責任を回避します。
静的型、実際の型、メソッドレシーバーの3つの概念を理解します。
Person student= new Student();
student.work();
静的型は、コンパイル時にオブジェクトが属するとコンパイラが考える型です。これは主に宣言された型によって決定されるため、上記のPersonは静的型です。
実際の型は、実際のオブジェクトに基づいてインタプリタによって決定されます。実行中に参照によってポイントされるため、Studentが実際の型です。
メソッドレシーバーは、studentなど、動的バインディングによってこのメソッドを実行することが判明したオブジェクトです。
また、コンパイルされたクラスファイル内のバイトコードのメソッド呼び出し命令を理解してください。
(1)invokestatic:静的メソッドを呼び出す
(2)invokespecial:インスタンスコンストラクターメソッド、プライベートメソッドを呼び出す。
(3)Invokevirtual:すべての仮想メソッドを呼び出します。
(4)Invokeinterface:interfaceメソッドを呼び出します。このインターフェースを実装するオブジェクトは、実行時に決定されます。
(5)Invokedynamic:最初に、実行時に呼び出しポイント修飾子によって参照されるメソッドを動的に解決してから、メソッドを実行します。
非仮想メソッド:オーバーライドまたはオーバーライドできないメソッド。構築メソッド、静的メソッド、プライベートメソッド、および最終的に変更されたメソッドを指します。
仮想メソッド:オーバーライドできるメソッド。通常はインスタンスメソッドを指します。
例
package com.learn.pra06;
class Demo01{
public void method1(){
System.out.println("This is father non-static");
}
public static void method2(){
System.out.println("This is father static");
}
}
public class Demo02 extends Demo01{
public void method1(){
System.out.println("This is son non-static");
}
public static void method2(){
System.out.println("This is son static");
}
public static void main(String[] args){
Demo01 d1= new Demo01();
Demo02 d2= new Demo02();
Demo01 d3= new Demo02(); //父类引用指向子类对象
d1.method1();
d1.method2();
d2.method1();
d2.method2();
d3.method1();
d3.method2();
}
}
演算結果:
このような演算結果の最初の5行は、従来の考え方で理解できることは間違いありませんが、最後の1行はどうですか?動的バインディングについてはどうですか、冗談ですか?いいえ、ここでは動的バインディングは発生しません。また、静的メソッドで動的バインディングが発生しないのはなぜですか。動的バインディングはどうなりましたか?それはブレインストーミングです。正直なところ、私も盲目であり、次は正しいかもしれないし、そうでないかもしれませんが、それは常に真実です。
分析
上記のmainメソッドのバイトコードを最初に見てください。
// access flags 0x9
public static main([Ljava/lang/String;)V
L0
LINENUMBER 19 L0
NEW com/learn/pra06/Demo01
DUP
INVOKESPECIAL com/learn/pra06/Demo01.<init> ()V
ASTORE 1
L1
LINENUMBER 20 L1
NEW com/learn/pra06/Demo02
DUP
INVOKESPECIAL com/learn/pra06/Demo02.<init> ()V
ASTORE 2
L2
LINENUMBER 21 L2
NEW com/learn/pra06/Demo02
DUP
INVOKESPECIAL com/learn/pra06/Demo02.<init> ()V
ASTORE 3
L3
LINENUMBER 22 L3
ALOAD 1
INVOKEVIRTUAL com/learn/pra06/Demo01.method1 ()V
L4
LINENUMBER 23 L4
INVOKESTATIC com/learn/pra06/Demo01.method2 ()V
L5
LINENUMBER 24 L5
ALOAD 2
INVOKEVIRTUAL com/learn/pra06/Demo02.method1 ()V
L6
LINENUMBER 25 L6
INVOKESTATIC com/learn/pra06/Demo02.method2 ()V
L7
LINENUMBER 26 L7
ALOAD 3
INVOKEVIRTUAL com/learn/pra06/Demo01.method1 ()V
L8
LINENUMBER 27 L8
INVOKESTATIC com/learn/pra06/Demo01.method2 ()V
L9
LINENUMBER 28 L9
RETURN
L10
L + numberは、mainメソッドの本体の各行に対応しており、コードによって実行される命令をはっきりと見ることができます。それは単に愛であり、遅く会うための一種のラッシュがあります。
Demo01 d1= new Demo01();
実行時にこのステートメントはどうなりますか?早い段階で学習するために準備した命令セットを組み合わせます。上記のバイトコードを見ると、次のINVOKESPECIAL com/learn/pra06/Demo01.<init> ()V
ことがわかりました。Demo01のコンストラクターを呼び出すように依頼したいのですが、これについては疑いの余地がありません。L1についても同様です。
Demo01 d3= new Demo02();
宣言された型は親クラスですが、実際には新しい場合はサブクラスであり、同じバイトコードがこれに対応します。INVOKESPECIAL com/learn/pra06/Demo02.<init> ()V
d1.method1();
このステートメントは、インスタンスメソッドを呼び出すオブジェクトである必要があります。バイトコードもこの点を示しています。INVOKEVIRTUAL com/learn/pra06/Demo01.method1 ()V
ここではINVOKEVIRTUALが使用されています。つまり、仮想メソッドが呼び出され、このメソッドの参照がメソッドテーブルに格納されます(これについては後で詳しく説明します)。INVOKEVIRTUAL命令のみがメソッドテーブルに移動し、呼び出されるメソッドの参照を見つけます。
d1.method2();
この文は、オブジェクトが静的メソッドを呼び出すことです。バイトコードは次のとおりです。INVOKESTATIC com/learn/pra06/Demo01.method2 ()V
このメソッドは、メソッドテーブルを経由せずに、メソッド領域で静的メソッドを直接呼び出すことです。これは、静的メソッドの実行が静的メソッドにのみ依存することも説明しています。また、オーバーライドされたメソッド呼び出しは実際のタイプを参照するため、静的メソッドをオーバーライドすることはできません。d2の2つのメソッド呼び出しの説明は、d1と同じです。
d3メソッドの呼び出しプロセスに焦点が当てられています。d3.method1();
バイトコード:INVOKEVIRTUAL com/learn/pra06/Demo01.method1 ()V
INVOKEVIRTUAL命令が使用され、実際にポイントされているメソッドが操作中にメソッドテーブルで呼び出されることを示します。method01は書き換えられる可能性があるため、コンパイラはmethod1を示します。実行時に呼び出す必要があります。メソッドテーブルに格納されている実際のメソッドへの参照。method01メソッドはDemo02によって書き直されたため、メソッドテーブル内の親クラスのmethod01メソッドの参照は、サブクラスのmethod01メソッドに書き直されました。したがって、実行時にINVOKEVIRTUAL命令に従って検出されたmethod01メソッドはサブクラスです。
次にd3.method2();
、バイトコードを見て見つけます。INVOKESTATIC com/learn/pra06/Demo01.method2 ()V
INVOKESTATICを使用する場合、メソッドテーブルにはアクセスできませんが、親クラスのmethod2に直接アクセスするため、実行時に親クラスの静的メソッドが呼び出されます。
オブジェクトの静的型(宣言型)は、コンパイル時にメソッドのレシーバーとして使用されます。動作中は、命令セットに従って変更が行われます。
INVOKEVIRTUAL命令フロー
package com.learn.pra06;
public class ClassReference {
static class Person {
@Override
public String toString(){
return "I'm a person.";
}
public void eat(){
System.out.println("Person eat");
}
public void speak(){
System.out.println("Person speak");
}
}
static class Boy extends Person{
@Override
public String toString(){
return "I'm a boy";
}
@Override
public void speak(){
System.out.println("Boy speak");
}
public void fight(){
System.out.println("Boy fight");
}
}
static class Girl extends Person{
@Override
public String toString(){
return "I'm a girl";
}
@Override
public void speak(){
System.out.println("Girl speak");
}
public void sing(){
System.out.println("Girl sing");
}
}
public static void main(String[] args) {
Person boy = new Boy();
Person girl = new Girl();
System.out.println(boy);
boy.eat();
boy.speak();
System.out.println(girl);
girl.eat();
girl.speak();
}
}
実行結果:
BoyとGirlは親クラスのPerson eatメソッドをオーバーライドしなかったため、親クラスのeatメソッドを呼び出します。
バイトコード:
public static main([Ljava/lang/String;)V
L0
LINENUMBER 47 L0
NEW com/learn/pra06/ClassReference$Boy
DUP
INVOKESPECIAL com/learn/pra06/ClassReference$Boy.<init> ()V
ASTORE 1
L1
LINENUMBER 48 L1
NEW com/learn/pra06/ClassReference$Girl
DUP
INVOKESPECIAL com/learn/pra06/ClassReference$Girl.<init> ()V
ASTORE 2
L2
LINENUMBER 49 L2
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ALOAD 1
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
L3
LINENUMBER 50 L3
ALOAD 1
INVOKEVIRTUAL com/learn/pra06/ClassReference$Person.eat ()V
L4
LINENUMBER 51 L4
ALOAD 1
INVOKEVIRTUAL com/learn/pra06/ClassReference$Person.speak ()V
L5
LINENUMBER 53 L5
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ALOAD 2
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
L6
LINENUMBER 54 L6
ALOAD 2
INVOKEVIRTUAL com/learn/pra06/ClassReference$Person.eat ()V
L7
LINENUMBER 55 L7
ALOAD 2
INVOKEVIRTUAL com/learn/pra06/ClassReference$Person.speak ()V
L8
LINENUMBER 57 L8
RETURN
L9
明らかに、L2では、コンパイラーはルートクラスObjectのtoStringメソッドへの参照をクラスファイルに書き込みます。これは、コンパイラーがその近親者ではなく、祖先のメソッド参照を書き込むことを示します。
L3、L4、L6、およびL7はすべてINVOKEVIRTUALを使用しますが、プロセスは何ですか?
最初にメソッドテーブルのメモリモデルを
見てください。GirlメソッドテーブルとBoyメソッドテーブルを見ると、継承されたメソッドが最初から最後まで配置されており、メソッド参照のサブクラスに固定インデックスがあることがわかります。すべて同じオフセットを持ちます;サブクラスが親クラスのメソッドを書き換えると、元々親クラスに格納されていたサブクラスメソッドテーブルのメソッド参照が書き換えられたメソッドの参照になります。この時点で理解する必要があります。オブジェクトタイプに応じて正しいメソッドを呼び出すことができる理由メソッドの鍵はメソッドテーブルにあります。
以下をgirl.speak
例として、INVOKEVIRTUAL命令フローの
説明図を見てください
。1。まず、com / learn / pra06 / ClassReferenceに従って、INVOKEVIRTUAL com / learn / pra06 / ClassReferencePerson.speak()V
Person.speak()Vは、定数プール
2でメソッドのオフセットを見つけます。Personのメソッドテーブルをチェックして、メソッドテーブルのspeakメソッドのオフセットを取得します(15であると想定)。これにより、直接取得できます。メソッドへの参照。
3.これに従って、参照がGirlインスタンスを参照していると判断します
。4。次に、Girlインスタンスのメソッドテーブルに移動し、メソッド参照の値が原因で、上記のオフセットに従ってメソッドテーブルでメソッド参照を見つけます。メソッドの書き換えによって正しいメソッド参照が決定されたかどうかに応じてクラスにロードされるため、ここでメソッドを直接呼び出すことができます。
静的メソッドがインスタンスメソッドを非表示にできないのはなぜですか?
静的メソッドの呼び出しはコンパイラーで静的にバインドされ、インスタンスメソッドの呼び出しは実行時に動的にバインドされます。2つの呼び出し方法は異なるため、2つのうち1つしか存在できません。そうでない場合、あいまいさが生じます。
静的メソッドが静的メソッドを隠すことができるのはなぜですか?
呼び出しメソッドが同じであるため、上記のようなあいまいさは発生しません。親クラスとサブクラスは同じ関数を定義しますが、コンパイラはオブジェクトの静的タイプに応じて対応する静的メソッドの参照をアクティブにし、上書きの錯覚。書き直しではありません!
総括する
全体的なプロセスは次のとおりです。コンパイラはクラスをクラスファイルにコンパイルします。このファイルでは、メソッドは静的タイプに従って対応するメソッド参照をクラスに書き込みます。実行時に、JVMは定数プール内のメソッドの偏差を検出します。 INVOKEVIRTUAL Shiftが指すメソッド参照に従って、これに従って参照タイプが実際に指すオブジェクトを見つけ、このオブジェクトタイプのメソッドテーブルにアクセスし、オフセットに従ってターゲットメソッド参照が格納されている場所を見つけます。参照を取り出し、参照が実際に指しているメソッドを呼び出して、多態性を完成させます