色々
- 継承の概念は、先に学んだRubyと同じ感じの様子。
- 継承されるクラスを「スーパークラス」、継承してできる新しいクラスを「サブクラス」と呼ぶ
- 継承を用いて新しくサブクラスを定義するときは、「class サブクラス名 extends スーパークラス名」としてクラスを定義。例:class Bicycle extends Vehecle {
- extendsは「拡張する」という意味
- メインクラスにて呼び出したメソッドが、サブクラスに定義されていればサブクラスから、定義されていなければスーパークラスから呼び出される。
- オーバーライドという考え方があるが、長くなったので下段にまとめた。
- サブクラスでコンストラクタを定義するときには1つの決まりがある→「コンストラクタの先頭でスーパークラスのコンストラクタを呼びださなければならない」
- ↑うぜえな・・
- privateの代わりに、protectedを用いると、クラス内とサブクラスからのみアクセスを許すフィールドを作ることができる
- 以下、おさらい
- public: どこからでもアクセス可能
- protected: そのクラスと子クラス内からのみアクセス可能
- private: そのクラス内からのみアクセス可能
- 抽象メソッドというものがある(下段の下段参照)
- インスタンスフィールドにクラス型の変数を定義することで、フィールドにインスタンスを持つことが可能
- ↑今回の例だと、PersonクラスのインスタンスフィールドにfirstName等を定義して、Vehicleクラスのインスタンスフィールドにてprivate Person owner;と記述する感じ。これで、「Person型のownerフィールドを追加」したことになる
- ↑ややこしいのが、その時にゲッターとセッターを定義する歳、ゲッターの戻り値の型と、セッターの仮引数の型がクラス型になることに注意する。ゲッターなら「public Person getOwner(); return〜」、セッターなら「public void setOwner(Person person){this.owner=person;}」とする感じ。
- 例でbuyメソッド・ゲッター・セッターにて「Car(もしくはBicycle)を買ったのはこのPerson」というのを表示できるようになったが、じゃあトラックとかバイクとか増えたら、その度にbuyメソッドを増やすのか、という問題がある
- ↑それを解決するために引数としてVehicle型のインスタンスを受け取るようにすることで、Carクラスのインスタンスも、Bicycleクラスのインスタンスも受け取ることができるようになる
- Carクラスは、Vehicleクラスを継承しているので、CarクラスのインスタンスはCar型である前にVehicle型でもある
- この関係にあるとき、サブクラスのインスタンスを、スーパークラスのクラス型変数に代入することが可能になる
- このような特徴を「多態性(たたいせい)」という
オーバーライド関係
- 一瞬「聞いたことあるぞ!?」と思ったが、そっちはオーバーロードだった(引数の型とか数が違ったら別物になる的なやつ)
- スーパークラスから継承したメソッドと同名のメソッドをサブクラスに定義することで、スーパークラスのメソッドの内容を上書きすることができる(オーバーライド)
- ↑なんでそんな変なことするんでしょうか重複を避けるために継承の概念があるのではないでしょうか誰か教えて下さい(嘘です、後で調べます)
- オーバーライドの仕組み:サブクラスのインスタンスに対してメソッドを呼び出すと、まずサブクラスの中でそのメソッドを探し、持っていればそのメソッドを呼び出す→つまりスーパークラスと同名のメソッドがサブクラスにあれば、それが実行されるので、結果的にメソッドの内容が上書きされたようになる
- オーバーライドの際は、インスタンスフィールドの出力に注意。今やっている例だと、colorとかはVehicleクラスにprivateなフィールドとして定義されており、外部のクラスであるCarクラスから直接アクセスすることができない(カプセル化)。fuel以外のフィールドはゲッターを用いて取得するようにしよう。
- ↑やはりなんかムカつきますね、オーバーライド。
- 「super.メソッド名()」とすると、サブクラスのインスタンスメソッドから、スーパークラスのインスタンスメソッドを呼び出すことができる。今やっている例だと、これを使えば、CarクラスのprintDataメソッドで、ガソリン量を表示する箇所以外の処理はVehicleクラスのprintDataメソッドを呼び出すことで代用することができる
- ↑少し、オーバーライドを許そうと思った。
抽象メソッド
- 処理が未定のメソッドを定義する方法・・・メソッドにabstractをつける(抽象メソッド)。抽象メソッドには中身の処理は書かない
- 抽象メソッドは、サブクラスがそのメソッドをオーバーライド(上書き)していなければエラーになる。よって、サブクラスがそのメソッドをオーバーライドし、処理内容を定義することを強制できる
- サブクラスに、あるメソッドを必ず持たせたいという場合は、スーパークラスに抽象メソッドとして定義しておくことが大事
- 抽象メソッドを1つでも持つクラスは、「抽象クラス」と呼ばれ、クラス名の前にabstractをつける必要がある
- 抽象クラスはインスタンスを生成できない。抽象メソッドという未完成のメソッドを持つクラスは、それもまた未完成。そのような未完成のクラスからはインスタンスを生成できないような仕組みになっている
- なんか、考え方がかっこいいね。
ミスったところ
演習(最終版のみ)
class Main { public static void main(String[] args) { Person person1 = new Person("Kate", "Jones", 27, 1.6, 50.0); Person person2 = new Person("John", "Christopher", "Smith", 65, 1.75, 80.0); Car car = new Car("フェラーリ", "赤"); Bicycle bicycle = new Bicycle("ビアンキ", "緑"); person1.buy(car); person2.buy(bicycle); System.out.println("【車の情報】"); car.printData(); System.out.println("-----------------"); System.out.println("【車の所有者の情報】"); car.getOwner().printData(); System.out.println("================="); System.out.println("【自転車の情報】"); bicycle.printData(); System.out.println("-----------------"); System.out.println("【自転車の所有者の情報】"); bicycle.getOwner().printData(); } }
abstract class Vehicle { private String name; private String color; protected int distance = 0; private Person owner; Vehicle(String name, String color) { this.name = name; this.color = color; } public String getName() { return this.name; } public String getColor() { return this.color; } public int getDistance() { return this.distance; } public Person getOwner() { return this.owner; } public void setName(String name) { this.name = name; } public void setColor(String color) { this.color = color; } public void setOwner(Person person) { this.owner = person; } public void printData() { System.out.println("名前:" + this.name); System.out.println("色:" + this.color); System.out.println("走行距離:" + this.distance + "km"); } public abstract void run(int distance); }
class Bicycle extends Vehicle { Bicycle(String name, String color) { super(name, color); } public void run(int distance) { System.out.println(distance + "km走ります"); this.distance += distance; System.out.println("走行距離:" + this.distance + "km"); } }
class Car extends Vehicle { private int fuel = 50; Car(String name, String color) { super(name, color); } public int getFuel() { return this.fuel; } public void printData() { super.printData(); System.out.println("ガソリン量:" + this.fuel + "L"); } public void run(int distance) { System.out.println(distance + "km走ります"); if (distance <= this.fuel) { this.distance += distance; this.fuel -= distance; } else { System.out.println("ガソリンが足りません"); } System.out.println("走行距離:" + this.distance + "km"); System.out.println("ガソリン量:" + this.fuel + "L"); } public void charge(int litre) { System.out.println(litre + "L給油します"); if (litre <= 0) { System.out.println("給油できません"); } else if (litre + this.fuel >= 100) { System.out.println("満タンまで給油します"); this.fuel = 100; } else { this.fuel += litre; } System.out.println("ガソリン量:" + this.fuel + "L"); } }
class Person { private String firstName; private String middleName; private String lastName; private int age; private double height; private double weight; Person(String firstName, String lastName, int age, double height, double weight) { this.firstName = firstName; this.lastName = lastName; this.age = age; this.height = height; this.weight = weight; } Person(String firstName, String middleName, String lastName, int age, double height, double weight) { this(firstName, lastName, age, height, weight); this.middleName = middleName; } public String fullName() { if (this.middleName == null) { return this.firstName + " " + this.lastName; } else { return this.firstName + " " + this.middleName + " " + this.lastName; } } public void printData() { System.out.println("名前は" + this.fullName() + "です"); System.out.println("年齢は" + this.age + "歳です"); System.out.println("身長は" + this.height + "mです"); System.out.println("体重は" + this.weight + "kgです"); System.out.println("BMIは" + Math.round(this.bmi()) + "です"); } public double bmi() { return this.weight / this.height / this.height; } public void buy(Vehicle vehicle) { vehicle.setOwner(this); } }
↑の感想というか整理
Mainクラスで呼び出したbuyメソッドで頭ぐるぐるしてくるな。ちょい整理してみるか
//■ Mainクラス Person person1 = new Person("Kate", "Jones", 27, 1.6, 50.0); //Personクラスのコンストラクタですね。名前とか年齢を作る。2もあるけどシンプルに理解したいので省略。 Car car = new Car("フェラーリ", "赤"); //Carクラスのコンストラクタですね。 //ただ、Carクラスはサブクラスで、基本はスーパークラス(違った?)のVehicleクラスにある person1.buy(car); //person1は上記コンストラクタ //buyメソッドはPersonクラスにあるメソッド //引数がCarクラスのコンストラクタ。この辺から脳が染み出す //とりあえずbuyがなにか見てみよう //■ Personクラス public void buy(Vehicle vehicle) { vehicle.setOwner(this); } //Vehicle vehicleはVehicleクラス型の仮引数がvehicle、という意味で、今回の例だと、carということになる。 //そしてそのcarはコンストラクタであって、あれ?このへんでやばくなってきたぞ! //setOwnerはVehicleクラスにあるセッター。次に見よう。 //thisがやっぱりよく分かっていない。今回だとperson1なのか? //■ Vehicleクラス public void setOwner(Person person) { this.owner = person; } //Person personはPersonクラス型の仮引数がpersonという意味で、↑の流れでいくと、person1になるのか? //ownerってなんだ。探す。 //■ Personクラス private Person owner; //インスタンスフィールドですね。クラスフィールドだったっけ。このあたりはよく復習しないと。 //Personクラス型として、とりあえずでownerのフィールドを定義している、という感じか。とりあえず感が。 //■ Vehicleクラス public void setOwner(Person person) { this.owner = person; } //もう一度見ると、ここで、「とりあえず用意したownerにpersonを代入する、という意味になるな。 //この流れだと、やはりperson1ということになるのか。 //■ Mainクラス car.getOwner().printData(); //とりあえず、ゲッター見ようか。Vehicleクラスにあるようだ //■ Vehicleクラス public Person getOwner() { return this.owner; } //ownerを返すようになってますね。 //もちろん、返すのはセッターでセットしたownerなので、person1ということだな。 //■ Mainクラス car.getOwner().printData(); //もう一度見てみよう //carコンストラクタのあるCarクラスのスーパークラスであるVehicleクラスのコンストラクタを呼び出す //そこにあるゲッターにて戻り値を取得する。今回はperson1 //getOwner().printData();と書くと、PersonクラスのprintDataメソッドをやるようだ。この辺もムズい。 //なので、carをbuyしたperson(今回はperson1が引数なので彼です)の情報を出力してくれる訳だ。 // //ほんとに?(誰かコメントください)