Java の継承と合成の基本概念
継承: 既存のクラスに基づいて新しいクラスを構築できます。 。既存のクラスを継承すると、これらのクラスのメソッドとフィールドを再利用できます。これに基づいて、新しいメソッドとフィールドを追加することで、クラスの機能を拡張できます。
合成: 新しいクラスで元のオブジェクトを作成することを合成と呼びます。こうすることで、既存のコードの形式を変更せずに再利用できます。
推奨される関連ビデオ チュートリアル: java ビデオ チュートリアル
1. 継承された構文
キーワード extends は、新しいクラスが既存のクラスに派生します。既存のクラスは親クラスまたは基本クラスと呼ばれ、新しいクラスはサブクラスまたは派生クラスと呼ばれます。例:
class Student extends Person { }
クラス Student は Person を継承します。パーソン クラスは親クラスまたは基本クラスと呼ばれ、Student クラスはサブクラスまたは派生クラスと呼ばれます。
2. 合成の構文
合成は比較的単純で、クラス内に既存のクラスを作成します。
class Student { Dog dog; }
モデリングの更新
1. 基本概念
継承の役割は、コードの使用が重複している。継承とは、親クラスのすべてのメソッドが子クラスでも使用できることを意味するため、親クラスに送信されたメッセージは派生クラスにも送信できます。 Person クラスに Eat メソッドがある場合、Student クラスにもこのメソッドが存在します。これは、Student オブジェクトも Person のタイプであることを意味します。
class Person { public void eat() { System.out.println("eat"); } static void show(Person p) { p.eat(); } } public class Student extends Person{ public static void main(String[] args) { Student s = new Student(); Person.show(s); // ① } }
[実行結果]:
eat
PERSONハンドルを受け取るには、PERSONに定義されているshowメソッドを使用しますが、①で受け取るのはStudentオブジェクトへの参照です。これは、Student オブジェクトが person オブジェクトでもあるためです。 show メソッドでは、受信ハンドル (オブジェクト参照) は、person オブジェクトおよび person 派生クラス オブジェクトにすることができます。 Student ハンドルを Person ハンドルに変換するこの動作は、アップキャストと呼ばれます。
2. 形状をトレースバックする必要があるのはなぜですか?
eat を呼び出すときに、それを呼び出すオブジェクト型を意図的に無視するのはなぜですか? show メソッドが単純に Student ハンドルを取得する方が直観的で理解しやすいように思えますが、その場合、Person クラスから派生した新しいクラスごとに独自の show メソッドを実装することになります。
class Value { private int count = 1; private Value(int count) { this.count = count; } public static final Value v1 = new Value(1), v2 = new Value(2), v3 = new Value(3); } class Person { public void eat(Value v) { System.out.println("Person.eat()"); } } class Teacher extends Person { public void eat(Value v) { System.out.println("Teacher.eat()"); } } class Student extends Person { public void eat(Value v) { System.out.println("Student.eat()"); } } public class UpcastingDemo { public static void show(Student s) { s.eat(Value.v1); } public static void show(Teacher t) { t.eat(Value.v1); } public static void show(Person p) { p.eat(Value.v1); } public static void main(String[] args) { Student s = new Student(); Teacher t = new Teacher(); Person p = new Person(); show(s); show(t); show(p); } }
このアプローチの欠点は明らかです。それは、密接に関連したメソッドを Person クラスの派生クラスごとに定義する必要があるため、多くの重複コードが発生するということです。一方、メソッドをオーバーロードするのを忘れた場合、エラーは報告されません。上記の例の 3 つの show メソッドは 1 つに結合できます。
public static void show(Person p) { p.eat(Value.v1); }
動的バインディング
show が実行されると、出力結果は Student.eat() で、これは確かに望ましい結果ですが、必要な形式で実行されないようです。show メソッドを見てみましょう:
public static void show(Person p) { p.eat(Value.v1); }
これは Person を受け取ります。実行時 show(s) のとき、パーソン ハンドルが Teacher オブジェクトではなく Student オブジェクトを指していることをどのようにして知るのでしょうか?コンパイラーにはそれを知る方法がありません。これには、次に説明するバインディングの問題が関係します。
1. メソッド呼び出しのバインディング
メソッドとメソッド本体を接続することをバインディングと呼びます。実行前にバインドが実行される場合、それは「早期バインディング」と呼ばれます。上記の例では、Person ハンドルが 1 つしかない場合、コンパイラーはどのメソッドを呼び出すべきかわかりません。 Java は、実行時にオブジェクトの型を決定し、対応するメソッドを呼び出すことができるメソッド呼び出しメカニズムを実装しています。実行時に実行され、オブジェクトの型に基づいて実行されるこのバインディングは、動的バインディングと呼ばれます。メソッドがfinal宣言されていない限り、Javaのすべてのメソッドは動的にバインドされます。
上流モデリングの継承関係を図で表現します:
コードで要約します:
Shape s = new Shape();
継承関係に従ってCircle は Shape の一種であるため、Circle オブジェクト ハンドルを Shape に割り当てることは正当です。
基本クラス メソッドの 1 つが呼び出されるとき:
Shape s = new Shape();
このとき、Circle.draw() が呼び出されます。これは動的バインディングによるものです。
class Person { void eat() {} void speak() {} } class Boy extends Person { void eat() { System.out.println("Boy.eat()"); } void speak() { System.out.println("Boy.speak()"); } } class Girl extends Person { void eat() { System.out.println("Girl.eat()"); } void speak() { System.out.println("Girl.speak()"); } } public class Persons { public static Person randPerson() { switch ((int)(Math.random() * 2)) { default: case 0: return new Boy(); case 1: return new Girl(); } } public static void main(String[] args) { Person[] p = new Person[4]; for (int i = 0; i < p.length; i++) { p[i] = randPerson(); // 随机生成Boy或Girl } for (int i = 0; i < p.length; i++) { p[i].eat(); } } }
person は、person から派生したすべてのクラスに共通のインターフェイスを確立しており、すべての派生クラスには食べると話すという 2 つの動作があります。派生クラスはこれらの定義をオーバーライドし、両方の動作を再定義します。
メイン クラスでは、randPerson が Person オブジェクトのハンドルをランダムに選択します。 **アピールの形成は return ステートメントで行われます。 **return ステートメントは、Boy または Girl ハンドルを取得し、それを Person タイプとして返します。この時点では、特定のタイプはわかりません。それが Person オブジェクト ハンドルであることだけがわかります。
main メソッドで randPerson メソッドを呼び出して、配列に Person オブジェクトを代入しますが、具体的な状況がわかりません。配列の各要素の Eat メソッドが呼び出されたときに、動的バインディングの役割は、オブジェクトの再定義されたメソッドを実行することです。
ただし、動的バインディングには前提条件があり、バインディング メソッドは基本クラスに存在する必要があり、存在しない場合はコンパイルできません。
class Person { void eat() { System.out.println("Person.eat()"); } } class Boy extends Person { void eat() { System.out.println("Boy.eat()"); } void speak() { System.out.println("Boy.speak()"); } } public class Persons { public static void main(String[] args) { Person p = new Boy(); p.eat(); p.speak(); // The method speak() is undefined for the type Person } }
如果子类中没有定义覆盖方法,则会调用父类中的方法:
class Person { void eat() { System.out.println("Person.eat()"); } } class Boy extends Person { } public class Persons { public static void main(String[] args) { Person p = new Boy(); p.eat(); } }
【运行结果】:
Person.eat()
2.静态方法的绑定
将上面的方法都加上static关键字,变成静态方法:
class Person { static void eat() { System.out.println("Person.eat()"); } static void speak() { System.out.println("Person.speak()"); } } class Boy extends Person { static void eat() { System.out.println("Boy.eat()"); } static void speak() { System.out.println("Boy.speak()"); } } class Girl extends Person { static void eat() { System.out.println("Girl.eat()"); } static void speak() { System.out.println("Girl.speak()"); } } public class Persons { public static Person randPerson() { switch ((int)(Math.random() * 2)) { default: case 0: return new Boy(); case 1: return new Girl(); } } public static void main(String[] args) { Person[] p = new Person[4]; for (int i = 0; i < p.length; i++) { p[i] = randPerson(); // 随机生成Boy或Girl } for (int i = 0; i < p.length; i++) { p[i].eat(); } } }
【运行结果】:
Person.eat() Person.eat() Person.eat() Person.eat()
观察结果,对于静态方法而言,不管父类引用指向的什么子类对象,调用的都是父类的方法。
更多java相关文章请关注java基础教程栏目。
以上がJavaの継承について詳しく解説の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。