Javaプログラミングの学習を進めていくと、必ずと言っていいほど立ちはだかる壁があります。それが「オブジェクト指向の3大要素」と呼ばれるものです。
「3大要素」と聞くと、なんだか難解な専門用語がたくさん出てきそうで身構えてしまいそうですが、これらの正体は「クラスという設計図を、もっと安全に、もっと便利に使い回すための3つのルール」に過ぎません。
本記事では、オブジェクト指向の3大要素の役割と仕組みをわかりやすく解説していきます。
オブジェクト指向の3大要素とは
オブジェクト指向の3大要素を理解するには、まずその大前提となる「クラス」について知っておく必要があります。
クラスとは、一言でいうと「システムを構成する部品の設計図」のようなものです。
クラスの詳しい仕組みについては以下の記事で解説しています。
実際のシステム開発の場面では、システムの規模が大きくなるほど、この設計図(クラス)が何十、何百と増えていくことになります。
クラスが増えれば増えるほど、「他の人が作った部品を勝手にいじって壊してしまった」「似たような設計図を何度もイチから書いてしまう」といった問題が発生しやすくなります。
そこで、大量のクラスを「安全に」「効率よく」「柔軟に」連携させるためのルールとして生み出されたのが、オブジェクト指向の3大要素です。
具体的には、以下の3つの仕組みを指します。
カプセル化: データを隠してバグを防ぐ(安全)
継承: 既存の設計図を引き継いで楽をする(再利用)
ポリモーフィズム: 同じ命令で違う動きをさせ、変更に強くする(柔軟)
これら3つのルールを守ることで、どれだけシステムが巨大になっても、壊れにくくメンテナンスしやすいプログラムを作ることができます。
それでは、それぞれの仕組みについて順番に見ていきましょう。
カプセル化(データを守る仕組み)
カプセル化とは、一言でいうと「クラスの中にある重要なデータを隠し、外部から勝手に書き換えられないように守る仕組み」のことです。
システム開発において、「他の人に重要なデータを書き換えられてしまう」という事故はよく起こります。
例えば、「車」クラスに「ガソリン残量」という重要なデータがあったとします。
重要データが誰でも外から直接書き換えられる状態だと、他の人が書いたプログラムが誤って「ガソリンをマイナス100リットルにする」といったあり得ない数値を入れ、システムにバグを起こす危険があります。
Javaでの実装イメージ
これを防ぐため、Javaでは重要な属性(データ)に private(プライベート)という鍵をかけます。
クラス(Car)の作成
public class Car {
// privateをつけると、他のクラスから直接アクセスできなくなる(隠す)
private int gas;
// 代わりに、「給油する」という専用の操作窓口(メソッド)を用意する
public void addGas(int amount) {
if (amount > 0) {
this.gas += amount; // マイナスの値は弾き、安全な数値だけを受け入れる
}
}
}
呼び出し側での動き(直接変更 と 窓口メソッドの利用)
では、このクラスを使う側(呼び出し側)はどのようになるのでしょうか?実際のコードで見てみましょう。
public class Main {
public static void main(String[] args) {
Car myCar = new Car();
// ❌ 悪い例:直接書き換えようとする
// myCar.gas = -100;
// ↑ gasは「private」で隠されているため、ここでエラー(コンパイルエラー)になり、書き換えを防げます!
// ⭕️ 良い例:用意された窓口(メソッド)を使う
myCar.addGas(20);
// ↑ 条件を満たす安全な数値なので、正しくガソリンが20追加される
// もし誤ってマイナスの値を入れようとしても...
myCar.addGas(-100);
// ↑ 窓口(addGasメソッド)の中にあるif文で弾かれるため、データは安全に守られます。
}
}
このように、「データは隠し、安全な操作窓口だけを公開する」ことで、他の人が誤った使い方をしようとしても未然に防いでくれる、バグの起きにくい頑丈なプログラムを作ることができます。
継承(コードを使い回す仕組み)
継承とは、「すでに完成しているクラス(設計図)をベースにして、新しいクラスを作る仕組み」のことです。
基準となる「車」のプログラムが完成した後に、新しく「トラック」のプログラムを作る場面を想像してください。
またイチから「走る」仕組みを作るのは時間の無駄ですよね。
そこで、すでに完成している車の仕組みを「引き継ぐ」ことで、開発スピードを劇的に上げる仕組みこそが継承です。
ベースとなる親クラスが持つ「属性」や「メソッド」を、子クラスがそのまま受け継ぎます。
Javaでの実装イメージ
Javaでは、新しいクラスを定義する際に extends (エクステンズ)というキーワードを使って、引き継ぐ親クラスを指定します。
まずはベースとなる親クラス(Car)を作成します。
親クラス(Car)の作成
public class Car {
// 属性(データ)
String name;
// メソッド(振る舞い)
public void run() {
System.out.println(name + "が走ります");
}
}
次に、親クラス(Car)を引き継ぐ子クラス(Truck)を作成します。
子クラス(Truck)の作成
// extends Car と書くことで、Carの属性とメソッドをすべて引き継ぐ
public class Truck extends Car {
// トラック専用の機能だけを新しく書き足す
public void carryCargo() {
System.out.println(name + "が荷物を運びます");
}
}
呼び出し側での動き(引き継いだ機能を使ってみる)
public class Main {
public static void main(String[] args) {
// トラックの実体(オブジェクト)を生成
Truck myTruck = new Truck();
// ⭕️ 親から引き継いだ「属性」にデータをセット
myTruck.name = "10トントラック";
// ⭕️ 親から引き継いだ「メソッド」
myTruck.run();
// ⭕️ 子クラス専用の「メソッド」
myTruck.carryCargo();
}
}
実行結果
10トントラックが走ります //親から引き継いだ「メソッド」run による出力
10トントラックが荷物を運びます //子クラス専用の「メソッド」carryCargo による出力
このように、共通するコード(名前という属性や、走る機能など)を何度も書く手間を省き、足りない機能(荷物を運ぶ機能)だけを付け足すことで、効率よくスピーディに開発を進めることができます。
ポリモーフィズム / 多様性(柔軟に対応する仕組み)
3つ目の中で一番名前が難しそうなポリモーフィズムですが、意味は「同じ命令を出しても、相手(クラス)によって違う動きをしてくれる仕組み」のことです。
システム開発では「後から新しい機能が追加される(仕様変更)」ことがよくあります。
例えば、「ガソリン車」だけでなく後から「電気自動車」が増えたとします。
このとき、メインのプログラム側で「もしガソリン車なら...」「もし電気自動車なら...」と条件分岐を書かずに、とりあえず「走れ」と命令するだけで済むようにするのがポリモーフィズムの力です。
Javaでの実装イメージ
親クラス(Car)の「走る」という機能を、それぞれの子クラスで上書き(オーバーライドといいます)して子クラスの専用の動きに変更します。
まずは親クラス(Car)を作成します。
親クラス(Car)の作成
public class Car {
public void run() {
System.out.println("走ります");
}
}
次に子クラス(ガソリン車 GasCar と電気自動車 ElectricCar)を作成しましょう。
子クラス①(GasCar)の作成
// ガソリン車クラス
public class GasCar extends Car {
// 親の機能を上書きする
public void run() {
System.out.println("ガソリンでエンジンを動かして走ります!");
}
}
子クラス②(ElectricCar)の作成
// 電気自動車クラス
public class ElectricCar extends Car {
// 親の機能を上書きする
public void run() {
System.out.println("電気でモーターを回して走ります!");
}
}
呼び出し側での動き(同じ命令を出してみる)
ここがポリモーフィズムの最大の見せ場です。
メインプログラム(使う側)では、相手がどの車種かを気にせず「走れ」とだけ命令します。
public class Main {
public static void main(String[] args) {
// 親クラスである「Car(車)」という大きなくくりで扱うことができる
Car car1 = new GasCar();
Car car2 = new ElectricCar();
// どちらもまったく同じ「run()」という命令を出す
car1.run();
car2.run();
}
}
実行結果
ガソリンでエンジンを動かして走ります! // car1.run() による出力
電気でモーターを回して走ります! // car2.run() による出力
このように、メインプログラム側で「もしガソリン車ならエンジンを〜」「もし電気自動車ならモーターを〜」といった複雑な条件分岐(if文など)を書く必要がありません。
メインプログラムから「とりあえず『走れ』と命令すれば、あとは各クラスが勝手に自分の走り方で走ってくれる」ため、後から新しい車種(ハイブリッド車など)を追加する際も非常に簡単になります。
おわりに

本記事では、オブジェクト指向の3大要素である「カプセル化」「継承」「ポリモーフィズム」について解説しました。
カプセル化: データを隠してバグを防ぐ(安全)
継承: 既存の設計図を引き継いで楽をする(再利用)
ポリモーフィズム: 同じ命令で違う動きをさせ、変更に強くする(柔軟)
これらはすべて、「システムが大きくなっても、安全に、効率よく開発を進めるための仕組み」です。
初めのうちは、コードの書き方(privateやextendsなどの文法)を完璧に暗記する必要はありません。
「なぜこの仕組みがあるのか?」という目的を理解しておくことが、オブジェクト指向プログラミングをマスターするための一番の近道です。
最初は難しく感じるかもしれませんが、実際にコードを書いて動かしてみることで、少しずつ理解が深まっていきます。
ぜひ今回の内容を、今後のプログラミング学習にお役立てください!