Javaのオブジェクト指向がわからない――。Java学習で誰もが最初にぶつかる壁です。
私もJavaを書き始めた当初、「車はクラスで赤い車はインスタンス」という説明を読んでも、自分のコードにどう活かせばいいのか見当もつきませんでした。結局、単語を覚えようとしていたのが間違いで、「仕組み」を理解してから一気に視界が開けたのを覚えています。
この記事では10年以上の開発経験をもとに、カプセル化・継承・ポリモーフィズムの3大要素をコード付きで解説します。さらに初級〜応用の練習問題3問で手を動かしながら定着させます。
この記事では以下について解説します:
Javaのオブジェクト指向とは何か?

Javaのオブジェクト指向とは、一言で言えば「現実世界のモノやコトを、独立した部品としてプログラム内で再現する考え方」です。
プログラミングの世界には、古くから「手続き型」という考え方がありました。手続き型は上から下へ順番に命令を並べていくスタイルです。
小さなプログラムなら手続き型で十分です。ただし規模が大きくなると「どこで何を変えたかわからない」というパニック状態に陥ります。
手続き型の問題を解決したのがオブジェクト指向です。プログラムを「命令の羅列」ではなく「役割を持ったモノ(オブジェクト)の集まり」として捉えます。
例えば、あなたがRPGゲームを作るとしましょう。主人公、モンスター、アイテム、魔法。全部を1つの巨大なファイルに書き込むのは正気の沙汰ではありません。
「主人公オブジェクト」「モンスターオブジェクト」と役割ごとに切り分けます。切り分けたオブジェクトが互いにメッセージを送り合って動くように設計するのです。
「部品化」がオブジェクト指向の核心です。
部品ごとに分かれているから、主人公の攻撃力を修正してもモンスターの動きには影響しません。影響範囲を限定できる点が、大規模開発で開発者がJavaを選び続ける最大の理由です。
2026年時点でも、エンタープライズ開発の現場ではJava 21・25(LTSバージョン)が主流です。Java 8・11からの移行はほぼ完了し、17から21・25への移行が進んでいます。オブジェクト指向という基本思想はどのバージョンでも変わりません。
オブジェクト指向と手続き型プログラミングの違い
オブジェクト指向を理解するには、従来の「手続き型プログラミング」と比較するとわかりやすくなります。
手続き型プログラミングは、処理を上から順番に書いていくスタイルです。「AをしてBをしてCをする」という直線的な流れで、小さなプログラムならシンプルに書けます。
一方、オブジェクト指向は「データとそのデータを操作する処理」をひとまとめにして部品化します。規模が大きくなるほど、部品化の恩恵が効いてきます。
| 比較項目 | 手続き型 | オブジェクト指向 |
|---|---|---|
| コードの管理 | 処理が一箇所に集中しやすい | 役割ごとにクラスで分離 |
| 再利用性 | 関数コピーが中心 | 継承・インターフェースで柔軟に再利用 |
| 拡張性 | 変更時に影響範囲が広い | 新しいクラスを追加するだけで拡張可能 |
| 向いている規模 | 数百行の小規模ツール | 数万行以上の大規模システム |
「手続き型がダメ」というわけではありません。シェルスクリプトや簡単なバッチ処理など、小規模な用途では手続き型の方がシンプルに書けます。規模が大きくなり複数人で開発するようになったとき、オブジェクト指向の設計が威力を発揮します。
クラスとインスタンスで理解するオブジェクト指向
より具体的に、私たちの身近な「犬」を例にして考えてみましょう。プログラムの中で1匹の犬を再現したいとき、その犬が持つ特徴と行動を整理します。
- 属性(フィールド):名前、年齢、犬種
- 振る舞い(メソッド):吠える、歩く、食べる
モノが持つ「状態」と「動き」をひとまとめにしたものがオブジェクトです。
オブジェクト指向で重要なのは、「設計図」と「実体」を明確に分ける点です。設計図はあくまで「犬とはこういうものだ」という定義にすぎません。実際にエサを食べたり吠えたりするのは、設計図から生み出された具体的な犬たちです。「ポチ」や「シロ」といった、名前を持つ一匹一匹がそれにあたります。
「設計図(クラス)」から「実体(インスタンス)」を作るプロセスが、Javaを扱う上での第一歩です。クラスとインスタンスの関係が理解できると、Javaのコードがただの文字列ではなく、データと処理が連動する仕組みに見えてきます。
クラスの仕組みがあれば、1,000匹の犬を管理するのも恐れるに足りません。
Javaオブジェクト指向のサンプルコード
では、実際に犬をオブジェクトとして表現したJavaのコードを見てみましょう。頭の中でイメージした「属性」と「振る舞い」が、どのようにコードになるかを確認してください。
// クラス(設計図)の定義
class Dog {
// 属性(フィールド)
String name;
int age;
// コンストラクタ(設計図から実体を作る時の初期設定)
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
// 振る舞い(メソッド)
public void bark() {
System.out.println(name + "がワン!と吠えた");
}
// 振る舞い(メソッド)
public void walk() {
System.out.println(name + "が散歩している");
}
}
// 実行用のクラス
public class Main {
public static void main(String[] args) {
// Dogという設計図から、"ポチ"という実体(インスタンス)を生成
Dog myDog = new Dog("ポチ", 5);
// 生成したオブジェクトに指示を出す
myDog.bark(); // ポチがワン!と吠えた
myDog.walk(); // ポチが散歩している
}
}このコードの面白いところは、Mainクラス側では「犬がどうやって吠えるか」という詳細を知る必要がない点です。単にbark()と指示を出すだけで、犬オブジェクトが自分自身の名前を使って適切に動いてくれます。
もし「柴犬」や「チワワ」など、別の犬を増やしたくなったら、同じDogクラスを使って新しいインスタンスを作るだけです。
コンストラクタ(オブジェクトを生まれながらに設定する)
先ほどのコードにpublic Dog(String name, int age)という部分がありました。Dog(String name, int age)がコンストラクタです。
コンストラクタは、インスタンス生成時に自動で呼び出される「初期設定メソッド」です。new Dog("ポチ", 5)が実行された瞬間に動き、「生まれた瞬間に名前と年齢が決まっている」という自然な設計を実現できます。
コンストラクタがないと、インスタンスを生成した後にmyDog.name = "ポチ"と一つひとつ設定しなければならず、設定し忘れによるバグの温床になります。コンストラクタを使えば「生まれた瞬間から完全体」になるのです。
なお、Java 16以降ではフィールドとコンストラクタを自動生成するrecordクラスが使えます。record Dog(String name, int age) {}と書くだけで済みます。コンストラクタ・getter・equals・hashCodeがすべて自動で用意されます。「ただデータを持つだけのクラス」にはrecordが便利です。
Javaのオブジェクト指向を構成する3大要素
オブジェクト指向には、理解を深めるための「3大要素」と呼ばれる柱があります。3つの柱がカプセル化・継承・ポリモーフィズムです。
初心者の頃の私は、カタカナ用語を聞くだけで「うっ……」となっていました。でも大丈夫。カプセル化・継承・ポリモーフィズムはすべて「プログラミングを楽にするための便利機能」に過ぎません。難しい理論として身構えず、「どうすれば楽ができるか?」という視点で見ていきましょう。
| 要素 | 一言で言えば | 得られるメリット |
|---|---|---|
| カプセル化 | データを守る | バグを封じ込める |
| 継承 | 親の財産を受け継ぐ | コードの重複を排除 |
| ポリモーフィズム | 同じ指示で異なる反応 | 呼び出し側を変更不要 |
それぞれの要素は独立しているわけではなく、お互いに補い合いながら1つのシステムを作り上げています。3つの要素を使いこなせるようになると、あなたのコードは一気に「プロっぽく」なります。
カプセル化
カプセル化とは、データとその操作を1つのカプセルに閉じ込め、外部から勝手に中身をいじられないように保護する仕組みです。
想像してみてください。銀行口座のオブジェクトがあるとして、誰でも自由に「残高」という変数を書き換えられたら大変なことになりますよね。勝手に100万円増やされたら嬉しいですが、銀行としては倒産ものです(笑)。
残高(データ)を「秘密」にして、あらかじめ用意された「預け入れ」や「引き出し」というメソッドを通してしか操作できないようにします。カプセル化の基本的な考え方です。
具体的には、フィールドにprivateを付け、読み書きはgetter/setterという専用窓口で行います。setterにバリデーション(例:「名前は空にできない」)を仕込めるのが大きな利点です。不正なデータがオブジェクトに入り込むのを水際で防ぐ。これがカプセル化のポイントです。
class Animal {
private String name; // privateにして外部からの直接アクセスを遮断
// データを安全に取得するための窓口
public String getName() {
return name;
}
// データを安全に設定するための窓口(バリデーションが可能)
public void setName(String newName) {
if (newName != null && !newName.isEmpty()) {
name = newName;
} else {
System.out.println("名前が空ですよ!");
}
}
}「中身は見せない、でも使い方は教える」というスタンスを貫くことで、プログラムの部品としての独立性が高まります。外部の影響を受けにくくなるため、バグの発見も格段に早くなります。
実務で痛い目に遭った話があります。あるプロジェクトで、フィールドをpublicのまま公開していたクラスがありました。別のクラスから直接値を書き換えられ、想定外のデータが混入。原因の特定に丸1日かかりました。privateにしてsetter経由でバリデーションを入れていれば、数分で済んだはずです。カプセル化は「面倒な作法」ではなく「事故保険」です。
継承
継承とは、あるクラスの機能を引き継いで、新しいクラスを作る仕組みです。いわば「差分プログラミング」ですね。
例えば「動物」という親クラスを作ったとします。「名前」という属性や「食べる」という共通の振る舞いがあります。
「犬」や「猫」のクラスを作るとき、名前や食べる機能を一から書くのはとても非効率です。同じコードを何度も書くと、プログラマーが最も嫌う「二重管理」の原因になります。
継承を使えば解決します。「犬は動物の一種である」という関係を持たせることで、動物クラスの機能をそのまま使いつつ、犬特有の「吠える」という機能だけを追加すれば済みます。
class Animal {
void speak() {
System.out.println("音を出す");
}
}
// Animalを継承してCatを作る
class Cat extends Animal {
// ここには何も書かなくてもspeak()が使える!
}
public class Main {
public static void main(String[] args) {
Cat a1 = new Cat();
a1.speak(); // 親クラスのメソッドが実行される
}
}継承の最大のメリットは「共通化」です。「動物はすべて寝る前にあくびをする」という仕様変更があったとします。親クラスを1箇所直すだけで、犬・猫・ライオンなどすべての子クラスに修正が反映されます。
1箇所直せば全体に反映される。オブジェクト指向の面白さです。
ただし、現場には「継承よりコンポジション(委譲)を優先せよ」という原則があります。何でもかんでも継承すると階層が深くなりすぎ、親→子→孫→ひ孫とどこで何が起きているか追えなくなるからです。私の経験では、継承の階層は3段までが目安。それ以上深くなりそうなときは、インターフェースやコンポジションで設計を見直すサインです。
ポリモーフィズム(多態性)
ポリモーフィズムは、日本語で「多態性」や「多相性」と訳されます。少し難しく聞こえますが、要するに「同じ指示を出しても、相手によって振る舞いが変わる」という性質のことです。
例えば、あなたが「鳴け!」と指示を出す指揮者だとしましょう。相手が犬なら「ワンワン」、猫なら「ニャー」と鳴いてほしいはずです。でも、相手をいちいち確認して「犬さん、ワンワンと言ってください」「猫さん、ニャーと言ってください」と個別指示するのは非現実的ですよね。
ポリモーフィズムを使えば、「動物たち、鳴け!」という1つの命令で、それぞれの動物が自分の種類に合わせて勝手に鳴き分けてくれます。
class Animal {
void speak() {
System.out.println("鳴く");
}
}
class Cat extends Animal {
@Override
void speak() {
System.out.println("ニャー");
}
}
class Dog extends Animal {
@Override
void speak() {
System.out.println("ワンワン");
}
}
public class Main {
public static void main(String[] args) {
// Animalという大きな枠組みで、中身(実体)を入れ替える
Animal a1 = new Cat();
Animal a2 = new Dog();
a1.speak(); // ニャー
a2.speak(); // ワンワン
}
}ポリモーフィズムのすごいところは、後から「ペンギン」を追加しても呼び出し側のコードを一切修正しなくて済む点です。
クラスを追加しても呼び出し側を変えなくていい。変化の激しいシステム開発でポリモーフィズムが重宝される理由です。
実際、私が携わったECサイトでは決済方法(クレジットカード・銀行振込・コンビニ払い)をポリモーフィズムで設計していました。後からQRコード決済を追加する要件が来たとき、新しいクラスを1つ追加するだけで対応完了。既存の決済処理コードは1行も変更しませんでした。「拡張に開いて、修正に閉じる」を実感した瞬間です。
Javaのオブジェクト指向をさらに深める:インターフェースと抽象クラス
3大要素を理解できたら、次のステップとして「インターフェース」と「抽象クラス」を押さえておきましょう。実務のJava開発では、この2つが非常に頻繁に登場します。
インターフェースとは「約束事(契約)を定める仕組み」
インターフェースとは、「このクラスは○○という機能を必ず持つこと」という約束(契約)だけを定義する仕組みです。具体的な実装(中身の処理)は書きません。
// 「鳴ける」という約束を定義するインターフェース
interface Soundable {
void makeSound(); // 何をするかだけ宣言。中身は書かない
}
class Dog implements Soundable {
@Override
public void makeSound() {
System.out.println("ワンワン!");
}
}
class Cat implements Soundable {
@Override
public void makeSound() {
System.out.println("ニャー!");
}
}インターフェースの最大の価値はチーム開発で発揮されます。「Soundableインターフェースを実装する」と決めておけば、中身が完成していなくても「makeSound()を呼べばOK」とわかります。
複数のメンバーが並行して作業できるので、開発スピードが上がります。
抽象クラスとは「共通の骨格を提供する設計図」
抽象クラスは、インターフェースと継承の中間的な存在です。一部のメソッドに実装を持ちながら、サブクラスに「これは必ず自分で実装しなさい」と強制できます。
abstract class Animal {
String name;
Animal(String name) {
this.name = name;
}
// 共通の処理(すべての動物が持つ)
public void sleep() {
System.out.println(name + "が眠った");
}
// サブクラスに実装を強制する(抽象メソッド)
abstract void speak();
}
class Dog extends Animal {
Dog(String name) {
super(name);
}
@Override
void speak() {
System.out.println(name + ":ワンワン!");
}
}インターフェースと抽象クラスの使い分け
| 比較項目 | インターフェース | 抽象クラス |
|---|---|---|
| 実装の共有 | できない(Java 8以降はdefaultメソッドで可能、Java 9以降はprivateメソッドも追加。Java 17以降はsealed interfaceで実装範囲を制限可能) | できる |
| 多重継承 | 複数のインターフェースを実装できる | 1つしか継承できない |
| 使いどき | 「できること」を定義したいとき | 「共通の骨格」を持たせたいとき |
Javaのオブジェクト指向がなぜ重要なのか?
一言で言えば、「人間の脳には限界があるから」です。
「別にオブジェクト指向を使わなくても、動くプログラムは書けるじゃないか」と思うかもしれません。確かに、数百行程度のツールなら手続き型でも書けます。しかし、現場での開発は数万、数十万行の世界になります。
1人の人間が一度に把握できる情報量には限りがあります。
システム全体が複雑に絡み合っていると、どこか1箇所を直しただけで、全く関係のない場所でエラーが出る「デグレ(先祖返り)」が頻発します。
オブジェクト指向は、複雑なシステムを「理解可能な小さな部品」の集まりに変えてくれます。部品ごとに責任範囲が明確になっていれば、自分の担当する部品のことだけを考えればよくなります。「関心の分離」こそが、健全な開発環境を守る鍵です。
チーム開発で有効な設計力
一人で開発しているときは「あの変数はここで使っているな」と記憶を頼りにできますが、チーム開発ではそうはいきません。昨日入ってきたばかりの新人さんが、あなたが書いた大事な変数をうっかり書き換えてしまうかもしれません。
オブジェクト指向(特にカプセル化)を徹底していれば、うっかり書き換えによる事故を未然に防げます。「触っていい場所」と「隠しておくべき場所」が明確になるため、コードそのものが「使い方の説明書」になります。
カプセル化を使うと、こんな事故を防げます。
- 他のメンバーが重要な変数をうっかり書き換えてしまう
- どこで何を変えたかわからなくなる
- 修正が予期しない場所に影響する
インターフェースを活用すれば、詳細な実装の前にチーム全体で「部品の使い方」を共有できます。設計の共通言語として機能するため、認識のズレによる手戻りが減ります。
保守性と再利用性の向上
システムは作って終わりではありません。むしろ、作ってからの「運用・保守」の方が期間は長く、コストもかかります。
オブジェクト指向で設計されたコードなら、仕様変更にもすぐ対応できます。
- 「消費税率が変わった」→ 該当クラスを1箇所修正するだけ
- 「新しい割引サービスを追加したい」→ 新しいクラスを追加するだけ
以前のプロジェクトで、数千行のif-else文で書かれた「モンスター級のメソッド」を見たことがあります。何か1つ追加するたびに、すべての条件分岐を確認しなければならず、エンジニアたちは皆、吐き気を催しながら作業していました(笑)。
もしオブジェクト指向が適切に使われていれば、ポリモーフィズムを活用して、新しい条件を新しいクラスとして定義するだけで済んだはずです。「修正しやすさ」は、ビジネスの成功に直結する重要な要素です。
Javaのオブジェクト指向を学ぶ際にやるべきこと
概念はわかった。次は手を動かす番です。オブジェクト指向は「読んで理解する」ものではなく、「書いて体感する」ものです。
私も最初は本を読んで「わかった気」になっていましたが、いざ自分でクラスを作ろうとすると手が止まりました。何をクラスにすればいいのか、どこまでをメソッドにすればいいのか、その塩梅がわからなかったのです。
感覚を掴むためには、いくつかのステップを踏む必要があります。回り道に見えますが、基礎を固めることが結局は最短ルートです。
オブジェクト指向が難しいと感じる理由と乗り越え方
オブジェクト指向が難しいと感じるのは、あなただけではありません。多くの初学者がつまずく理由は大きく3つあります。
- 抽象概念が多い:カプセル化・継承・ポリモーフィズムなど、目に見えない概念を扱うため頭の中だけで理解しようとすると混乱する
- 用語が一気に増える:クラス・インスタンス・コンストラクタ・オーバーライドなど、覚えるべき言葉が短期間で押し寄せる
- 設計と実装のギャップ:「なぜクラスを分けるのか」が実感できないまま、教科書通りにコードを書くだけになりがち
乗り越えるコツは「完全に理解してから書く」のではなく「書きながら理解する」ことです。まずは10行のクラスを書いてみて、動かして壊して直す。そのサイクルを回すうちに、概念と手の感覚がつながっていきます。
サンプルコードで基本を体感する
まずは、既存のサンプルコードを書き写す(写経する)ことから始めましょう。自分でタイピングすることで、newキーワードの使い方やクラスの定義方法が指に馴染んできます。
ただ写すだけではなく「もしここをprivateに変えたらどうなるだろう?」「継承を外してみたらどう動くだろう?」と、意図的にコードを壊して実験してみてください。
エラーメッセージを読むことも、立派な学習の一部です。
おすすめは、先ほどの「犬」や「車」のような、現実のモノを模したシンプルなクラスを作ることです。
Personクラスを作って、名前や年齢を持たせ、introduce()メソッドで自己紹介させてみましょう。オブジェクト指向の第一歩として十分すぎるほどの価値があります。
自分で小さなアプリを作る
写経に慣れてきたら、次は自分の頭で考えて小さなアプリを作ってみましょう。立派なものである必要はありません。
- 電卓アプリ:数値を保持するクラスと、計算を行うクラスに分けてみる
- ToDoリスト:タスク一つ一つをオブジェクトとして扱い、リストで管理してみる
- ジャンケンゲーム:プレイヤーとCPUをオブジェクトにして、勝敗判定ロジックを分離してみる
「役割を分ける」ことを意識して設計してみると、オブジェクト指向のメリット(あるいは難しさ)を肌で感じることができます。
特に「ジャンケンゲーム」はおすすめです。「手(グー・チョキ・パー)」をクラスにするのか、それとも単なる数字にするのか。設計判断の積み重ねが、あなたのエンジニアとしての筋力を鍛えてくれます。
他人のコードを読む
ある程度書けるようになったら、ぜひプロが書いたコードを読んでみてください。GitHubには星の数ほど優れたJavaプロジェクトがあります。Spring FrameworkやApache Commonsなど、長年メンテナンスされているOSSは設計パターンの宝庫です。
「なぜこの人はここでインターフェースを使っているのか?」「この継承関係にはどんな意図があるのか?」と問いかけながら読むことで、自分一人では気づけなかった設計のパターンが見えてきます。
Javaの標準ライブラリ(java.util.Listなど)を読むのも勉強になります。
世界中のエンジニアが長年磨き上げたコードは、オブジェクト指向の教科書です。最初は難しく感じますが、「あ、ここポリモーフィズムだ!」と気づける瞬間が来れば、あなたの理解は本物です。
Javaオブジェクト指向の練習問題
オブジェクト指向は「書いて体感する」ものです。以下の練習問題で、実際に手を動かしてみましょう。解答例と解説も付けているので、詰まったら参考にしてください。
問題1: カプセル化の実装(初級)
次の仕様に従ってBankAccountクラスを作成してください。
- フィールド:
balance(残高)をprivateで定義 - メソッド:
deposit(int amount)(入金)/withdraw(int amount)(出金)/getBalance()(残高確認) - 出金時に残高不足の場合は「残高が不足しています」と表示し、残高を変更しない
- 入金額・出金額が0以下の場合は「金額が不正です」と表示
ヒント:カプセル化を使ってbalanceを守ることがポイントです。
解答例:
class BankAccount {
private int balance;
public BankAccount(int initialBalance) {
this.balance = initialBalance;
}
public void deposit(int amount) {
if (amount <= 0) {
System.out.println("金額が不正です");
return;
}
balance += amount;
System.out.println(amount + "円を入金しました。残高:" + balance + "円");
}
public void withdraw(int amount) {
if (amount <= 0) {
System.out.println("金額が不正です");
return;
}
if (amount > balance) {
System.out.println("残高が不足しています");
return;
}
balance -= amount;
System.out.println(amount + "円を出金しました。残高:" + balance + "円");
}
public int getBalance() {
return balance;
}
}解説:balanceをprivateにすることで、外部から直接account.balance = 999999のような不正な操作ができなくなります。deposit()とwithdraw()の中でバリデーションを行うことで、常に正しい状態を保てます。
実務Tips:setterを無条件に作るのは避けましょう。「本当に外部から変更される必要があるか?」を考えてからsetterを追加するのがベストプラクティスです。変更不要なフィールドはfinalにしてsetterを作らない(不変=immutable)方が安全です。
問題2: 継承を使ったクラス設計(中級)
「乗り物」という概念を、継承を使って表現してください。
- 親クラス
Vehicle:フィールドspeed(速度)、メソッドmove()(「速度○kmで移動中」と表示) - 子クラス
Car:Vehicleを継承し、honk()メソッド(「クラクションを鳴らした」と表示)を追加 - 子クラス
Bicycle:Vehicleを継承し、pedal()メソッド(「ペダルを漕いだ」と表示)を追加
解答例:
class Vehicle {
int speed;
Vehicle(int speed) {
this.speed = speed;
}
void move() {
System.out.println("速度" + speed + "kmで移動中");
}
}
class Car extends Vehicle {
Car(int speed) {
super(speed);
}
void honk() {
System.out.println("クラクションを鳴らした");
}
}
class Bicycle extends Vehicle {
Bicycle(int speed) {
super(speed);
}
void pedal() {
System.out.println("ペダルを漕いだ");
}
}
public class Main {
public static void main(String[] args) {
Car car = new Car(60);
car.move(); // 速度60kmで移動中
car.honk(); // クラクションを鳴らした
Bicycle bike = new Bicycle(15);
bike.move(); // 速度15kmで移動中
bike.pedal(); // ペダルを漕いだ
}
}解説:move()はVehicleクラスに一度だけ書けば、CarもBicycleも使い回せます。「乗り物の移動方法が変わった」ときも、Vehicleクラスを1箇所直すだけで全員に反映されます。
問題3: ポリモーフィズムの活用(応用)
問題2のCarとBicycleを使って、ポリモーフィズムを活用したコードを書いてください。Vehicle型の配列に複数の乗り物を格納し、ループでmove()を呼び出してください(呼び出し側はどの乗り物かを意識しない)。
解答例:
public class Main {
public static void main(String[] args) {
Vehicle[] vehicles = {
new Car(60),
new Bicycle(15),
new Car(100)
};
// 呼び出し側はCarかBicycleかを意識しない
for (Vehicle v : vehicles) {
v.move();
}
// 速度60kmで移動中
// 速度15kmで移動中
// 速度100kmで移動中
}
}解説:Vehicle型の配列にさまざまな乗り物を混在させられるのがポリモーフィズムの力です。将来「電車」クラスを追加しても、このループコードは一切変更不要。この姿勢が「拡張に開いて、修正に閉じる(オープン・クローズド原則)」という設計思想につながります。
まとめ:Javaのオブジェクト指向とは「設計の考え方」
最後に1つだけ。オブジェクト指向はあくまで「道具」です。
難解な用語や複雑な図解に惑わされないでください。すべては「人間が楽に、ミスなく、楽しく開発を続けるため」に先人のエンジニアが生み出した知恵です。
- Javaのオブジェクト指向は、現実をプログラムに落とし込むための「翻訳機」
- カプセル化でデータを守り、継承で楽をし、ポリモーフィズムで柔軟性を手に入れる
- インターフェース・抽象クラスを使いこなすと、チーム開発での協調がぐっとスムーズになる
- チーム開発での混乱を防ぎ、数年後の自分が泣かないための「備え」になる
完璧に理解してからコードを書こうとする必要はありません。汚いコードを書いて、苦労して、オブジェクト指向でリファクタリング(改善)する過程でこそ、真の理解は得られます。
私も未だに「この設計で本当に良かったのかな?」と悩むことがあります。でも、悩むこと自体が、より良いコードを書こうとするエンジニアの証です。
