やあ!Java の抽象クラスについて話しましょう。
なぜクラスは「抽象」と呼ばれるのでしょうか?
おそらく抽象化とは何かを覚えているでしょう - 前に説明しました :) 忘れても心配する必要はありません。クラスを設計してオブジェクトを作成するときは、エンティティの主なプロパティのみを表し、二次的なプロパティは破棄する必要があるという OOP の原則であることを思い出してください。たとえば、SchoolTeacher
クラスを設計している場合、身長は教師に必要な特性ではないでしょう。実際、この特性は教師にとって重要ではありません。しかし、クラスを作成する場合BasketballPlayer
、身長は最も重要な特性の 1 つになります。さて、抽象クラスは、将来のクラスのグループにとって最も抽象的な「大まかなワークピース」です。ワークピースは「粗すぎる」ため、直接使用することはできません。ただし、それは、将来のクラス (抽象クラスの子孫) が持つ特定の特徴的な状態と動作を定義します。
Java の抽象クラスの例
車の簡単な例を考えてみましょう。
public abstract class Car {
private String model;
private String color;
private int maxSpeed;
public abstract void gas();
public abstract void brake();
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public int getMaxSpeed() {
return maxSpeed;
}
public void setMaxSpeed(int maxSpeed) {
this.maxSpeed = maxSpeed;
}
}
最も単純な抽象クラスは次のようになります。ご覧のとおり、特別なことは何もありません :) なぜこれが必要なのでしょうか? まず、必要な実体、つまり車の最も抽象的な説明が提供されます。ここでの抽象的なキーワードは何かを意味します。現実の世界では、「ただの車」などというものは存在しません。トラック、レースカー、セダン、クーペ、SUV があります。私たちの抽象クラスは、後で特定の車のクラスを作成するために使用する単なる「青写真」です。
public class Sedan extends Car {
@Override
public void gas() {
System.out.println("The sedan is accelerating!");
}
@Override
public void brake() {
System.out.println("The sedan is slowing down!");
}
}
多くの点で、これは継承に関するレッスンで説明したことと似ています。この場合のみ、Car
メソッドが抽象ではないクラスがありました。ただし、このような解決策には、抽象クラスで修正されるいくつかの欠点があります。何よりもまず、抽象クラスのインスタンスは作成できません。
public class Main {
public static void main(String[] args) {
Car car = new Car(); // Error! The Car class is abstract!
}
}
Java の作成者は、この「機能」を意図的に作成しました。もう一度思い出してください。抽象クラスは、将来の「通常の」クラスの単なる青写真です。設計図のコピーは必要ありませんね? 同様に、抽象クラスのインスタンスを作成する必要はありません :) そして、Car
クラスが抽象クラスでない場合は、そのクラスのインスタンスを簡単に作成できます。
public class Car {
private String model;
private String color;
private int maxSpeed;
public void go() {
// ...some logic
}
public void brake() {
// ...some logic
}
}
public class Main {
public static void main(String[] args) {
Car car = new Car(); // This is okay. The car is created.
}
}
現在、私たちのプログラムにはある種の理解できない車が含まれています。トラックでも、レーシングカーでも、セダンでもないが、それが何なのかはよく分からない。現実には存在しない「ただのクルマ」です。同じ例が動物にも当てはまります。プログラムにAnimal
オブジェクト (「単なる動物」) があった場合を想像してください。それが何の種類で、どの科に属し、どのような特徴を持っているのかは明らかではありません。番組の中でそれを見るのは奇妙だろう。自然界には「ただの動物」は存在しません。犬、猫、キツネ、モグラなどだけ。抽象クラスは私たちを「単なるオブジェクト」から救います。これらはベースラインの状態と動作を示します。たとえば、すべての車にはモデル、色、最高速度が必要です。また、アクセルとブレーキをかけることもできなければなりません。それでおしまい。これは、後で必要なクラスを設計するために使用する一般的な抽象的なブループリントです。注:抽象クラスの 2 つのメソッドもabstractです。つまり、実装がまったくありません。理由は同じです。抽象クラスは「単なる車」の「デフォルトの動作」を作成しません。それらは、すべての車が実行できなければならないことを示しているだけです。ただし、デフォルトの動作が必要な場合は、抽象クラスにメソッドを実装できます。Java ではこれを禁止していません。
public abstract class Car {
private String model;
private String color;
private int maxSpeed;
public void gas() {
System.out.println("Accelerating!");
}
public abstract void brake();
// Getters and setters
}
public class Sedan extends Car {
@Override
public void brake() {
System.out.println("The sedan is slowing down!");
}
}
public class Main {
public static void main(String[] args) {
Sedan sedan = new Sedan();
sedan.gas();
}
}
コンソール出力:
Accelerating!
ご覧のとおり、抽象クラスに 1 つのメソッドを実装しましたが、もう 1 つは実装しませんでした。その結果、Sedan
クラスの動作は 2 つの部分に分割されます。メソッドを呼び出すとgas()
、動作は抽象Car
親クラスから「プルアップ」され、そのbrake()
メソッドをクラスに実装しますSedan
。それはとても便利で柔軟です。しかし、今のクラスはそれほど抽象的ではありませんね。結局のところ、メソッドの半分は実際に実装されました。実際、これは非常に重要な機能ですが、メソッドの 1 つでも抽象であれば、クラスは抽象です。。2 つの方法のうちの 1 つであるか、1,000 のうちの 1 つであるかは問題ではありません。抽象的なものを残さずにすべてのメソッドを実装することもできます。結果は、抽象メソッドのない抽象クラスになります。これは原理的には可能ですが、コンパイラはエラーを生成しません。しかし、abstract という単語の意味が失われるため、これは行わない方がよいでしょう。他のプログラマもこれを見て非常に驚くでしょう :/ つまり、メソッドが抽象としてマークされている場合、各子孫クラスはそれを実装するか、抽象として宣言する必要があります。それ以外の場合、コンパイラはエラーをスローします。もちろん、各クラスは 1 つの抽象クラスのみを継承できるため、継承に関しては抽象クラスと通常のクラスに違いはありません。抽象クラスを継承するか、通常のクラスを継承するかは関係ありません。親クラスは 1 つだけです。
Java に複数のクラス継承がない理由
Java には多重継承がないことはすでに述べましたが、その理由については詳しく掘り下げませんでした。今度はそれをやってみましょう。実際のところ、Java に多重継承がある場合、子クラスはどの動作を選択するかを決定できません。2 つのクラスがあるとします:Toaster
とNuclearBomb
:
public class Toaster {
public void on() {
System.out.println("The toaster is on. We're toasting!");
}
public void off() {
System.out.println("The toaster is off!");
}
}
public class NuclearBomb {
public void on() {
System.out.println("Boom!");
}
}
ご覧のとおり、両方のクラスにon()
メソッドがあります。トースターの場合はトーストを焼き始める方法ですが、核爆弾の場合は爆発を引き起こします。ああ :/ さて、その中間にあるものを作成しようと決めたと想像してください (理由は聞かないでください!)。あなたのクラスは次のとおりです: MysteriousDevice
! もちろん、このコードは機能しません。これを「あったかもしれないこと」の例として簡単に示します。
public class MysteriousDevice extends Toster, NuclearBomb {
public static void main(String[] args) {
MysteriousDevice mysteriousDevice = new MysteriousDevice();
mysteriousDevice.on(); // And what should happen here? Will we get toast or a nuclear apocalypse?
}
}
何が入っているか見てみましょう。この謎の装置は、トースターと核爆弾の両方から同時に派生したものです。どちらにも方法がありますon()
。その結果、オブジェクトを呼び出した場合にどの実装を実行する必要があるかが明確になりませon()
んMysteriousDevice
。オブジェクトは理解できません。さらに言えば、NuclearBomb にはメソッドがないoff()
ため、推測が正しくなければ、デバイスの電源を切ることは不可能になります。どの動作を実行すべきかが不明確な場合のこの「誤解」こそが、Java の作成者が多重継承を拒否した理由です。そうは言っても、Java クラスが多くのインターフェイスを実装できることがわかります。
GO TO FULL VERSION