クラスSub
は、クラスSup
のサブクラスです。それは実際にはどういう意味ですか?言い換えれば、「継承」の実際的な意味は何ですか?
オプション1:Supからのコードは、Subに事実上コピーされます。 ( 'copy-paste'と同様ですが、コピーされたコードなしvisuallyサブクラスで見られます)。
例:methodA()
は、もともとSupにあったメソッドです。 SubはSupを拡張するため、methodA()
は(実質的に)Subにコピーアンドペーストされます。 SubにはmethodA()
という名前のメソッドがあります。 コードのすべての行でSupのmethodA()
と同じですが、完全にSubに属しており、Supに依存していないか、何らかの方法でSupに関連しています。
オプション2:Supからのコードは、実際にはSubにコピーされていません。それはまだスーパークラスだけにあります。ただし、そのコードはサブクラスを通じてアクセスでき、サブクラスで使用できます。
例:methodA()
はSupのメソッドです。 SubはSupを拡張するので、methodA()
はSubからsubInstance.methodA()
のようにアクセスできます。しかし、実際にはスーパークラスのmethodA()
が呼び出されます。つまり、methodA()は、サブクラスによって呼び出された場合でも、スーパークラスのコンテキストで動作します。
質問:2つのオプションのどちらが実際に機能するのですか?どれもない場合は、実際にどのように機能するか説明してください。
オプション2。
バイトコードは実行時に動的に参照されます。これが、たとえば LinkageErrors が発生する理由です。
たとえば、2つのクラスをコンパイルするとします。
public class Parent {
public void doSomething(String x) { ... }
}
public class Child extends Parent {
@Override
public void doSomething(String x) {
super.doSomething(x);
...
}
}
次に、子クラスを変更または再コンパイルせずに、親クラスを変更して再コンパイルします。
public class Parent {
public void doSomething(Collection<?> x) { ... }
}
最後に、子クラスを使用するプログラムを実行します。 NoSuchMethodError を受け取ります:
アプリケーションがクラス(静的またはインスタンス)の指定されたメソッドを呼び出そうとし、そのクラスにそのメソッドの定義がなくなった場合にスローされます。
通常、このエラーはコンパイラーによってキャッチされます。このエラーは、クラスの定義が互換性なく変更された場合にのみ実行時に発生します。
2つの単純なクラスから始めましょう。
package com.michaelt.so.supers;
public class Sup {
int methodA(int a, int b) {
return a + b;
}
}
その後
package com.michaelt.so.supers;
public class Sub extends Sup {
@Override
int methodA(int a, int b) {
return super.methodA(a, b);
}
}
MethodAをコンパイルして、取得するバイトコードを確認します。
methodA(II)I
L0
LINENUMBER 6 L0
ALOAD 0
ILOAD 1
ILOAD 2
INVOKESPECIAL com/michaelt/so/supers/Sup.methodA (II)I
IRETURN
L1
LOCALVARIABLE this Lcom/michaelt/so/supers/Sub; L0 L1 0
LOCALVARIABLE a I L0 L1 1
LOCALVARIABLE b I L0 L1 2
MAXSTACK = 3
MAXLOCALS = 3
そして、SupクラスmethodA()に対してルックアップを行うinvokespecialメソッドですぐに確認できます。
invokespecial オペコードには次のロジックがあります。
この場合、クラスに同じ名前と記述子を持つインスタンスメソッドがないため、最初の箇条書きは実行されません。 2番目の箇条書きは、ただし、スーパークラスがあり、スーパーのmethodAを呼び出します。
コンパイラーはこれをインライン化せず、クラスにSupのソースのコピーはありません。
Howeverストーリーはまだ完成していません。これはcompiledコードです。コードがJVMに到達すると、 HotSpot が関与する可能性があります。
残念ながら、私はそれについてあまり知らないので、この件について当局に訴え、 Javaのインライン化 にアクセスします。ここで、HotSpotcanインラインメソッド(最終ではないメソッドも)。
docs に移動すると、特定のメソッド呼び出しが毎回そのルックアップを実行する代わりにホットスポットになる場合、この情報をインライン化して、Sup methodA()からSub methodAにコードを効果的にコピーできます。 ()。
これは、アプリケーションがどのように動作しているか、およびパフォーマンスを高速化するために必要な最適化に基づいて、実行時にメモリ内で行われます。
OpenJDKのHotSpot内部構造 で述べられているように、「メソッドはしばしばインライン化されます。静的、プライベート、最終、および/または「特別な」呼び出しは簡単にインライン化できます。」
JVMのオプション を掘り下げると、インライン化できる最大バイト数である-XX:MaxInlineSize=35
(35がデフォルト)のオプションが見つかります。これがJavaが多くの小さなメソッドを好む理由です-簡単にインライン化できるためです。これらの小さなメソッドは、canがインライン化されるため、より多く呼び出されるとfasterになります。そして、その数を試して大きくすることはできますが、他の最適化の効果が低下する可能性があります。 (関連するSO質問: HotSpot JITインライン戦略 これは、HotSpotが行っているインライン化の内部を覗くための他の多くのオプションを示しています)。
したがって、いいえ-コードはコンパイル時にインライン化されません。そして、はい-パフォーマンスの最適化が必要であれば、コードは実行時にインライン化される可能性が非常に高くなります。
そして、私がHotSpotインライン化について書いたものはすべて、Oracleが配布したHotSpot JVMにのみ適用されます。 wikipediaのJava仮想マシンのリスト を見ると、HotSpot以外にも多くの機能があり、これらのJVMがインライン化を処理する方法は、上記で説明した方法とはまったく異なる場合があります。 Apache Harmony、Dalvik、ART-状況によって動作が異なる場合があります。
コードはコピーされず、参照によってアクセスされます。
コンパイラはこれがメモリ内でどのように表現/実行されるかを最適化しますが、それは基本的には構造です