---
title: "オブジェクト指向とは？三本柱の実装手順とrecord・sealedまでJavaコードで解説"
url: "https://www.issoh.co.jp/tech/details/13412/"
published: 2026-07-10
updated: 2026-09-27
categories: ["Java"]
publisher: "株式会社一創"
---

# オブジェクト指向とは？三本柱の実装手順とrecord・sealedまでJavaコードで解説

オブジェクト指向とは、データ（状態）と処理（振る舞い）を「オブジェクト」という単位にまとめ、プログラムを部品の組み合わせとして設計する考え方です。中心になるのがカプセル化・継承・ポリモーフィズムの三本柱で、それぞれ担う役割がはっきり分かれています。この記事では三つを定義で終わらせず、Javaのアクセス修飾子やメソッド再定義まで落とし、公式ドキュメントに当たりながら動かせるコードとともに整理します。手続き型・データ指向との使い分け、継承の乱用、record・sealedによる型設計、保守運用で設計が効く条件まで扱いました。

## まとめ：三本柱の役割分担と、継承より合成を優先する判断軸

三本柱は目的が異なります。カプセル化は内部状態を隠してデータの整合性を守り、継承は共通コードを親にまとめて重複を減らし、ポリモーフィズムは呼び出し側のコードを変えずに振る舞いだけを差し替えます。設計で最初に効くのがカプセル化です。公開する窓口（メソッド）を絞るほど、後からの変更に強くなります。

実務での判断軸はひとつ、「継承を第一候補にしない」ことです。親子の型が本当に is-a（〜は〜の一種）で結べるときだけ継承を使い、コードの再利用が目的なら合成（フィールドとして別オブジェクトを持つ）を選びます。継承を安易に重ねた設計は、親の変更が子を壊す連鎖を生み、保守フェーズで最も高くつく典型例です。以下では各要素の実装コードと、この判断基準の根拠を順に説明します。

## オブジェクト指向が解決したい手続き型プログラミングの限界と背景

オブジェクト指向は1960年代のSimula、1980年代のSmalltalkを起点に広がり、C++・Java・C#・Python・Rubyなど主要言語の設計思想へ組み込まれました。生まれた動機は明確で、手続き型プログラミングが抱える「データと処理が分離したまま規模が膨らむ」問題を解くことにあります。

### オブジェクト指向が解決したかった手続き型の構造的な弱点と背景

手続き型では、データはグローバルな変数や構造体として置かれ、処理はそれを外から書き換える関数の集合になります。規模が小さいうちは読みやすい一方、データを触れる場所がコード全体に散らばるため、値が想定外に壊れたとき原因の特定に時間がかかる点が弱点です。仕様変更で1つのデータ構造に項目を足すと、それを参照する関数を横断的に直す必要が出ます。オブジェクト指向は、データとそれを操作する処理を同じオブジェクトへ閉じ込め、影響範囲をオブジェクト内部に限定することでこの弱点に対処しました。

### クラスとインスタンスの関係と、状態と振る舞いを束ねる基本単位

クラスは設計図、インスタンスはその設計図から作った実体です。このクラスの属性・操作や、クラス同士の関係をUMLの図として表す手法は[クラス図の書き方](https://www.issoh.co.jp/tech/details/13471/)で整理しました。クラスはフィールド（状態を持つ変数）とメソッド（振る舞いを表す関数）を1つの単位として定義し、`new` で生成したインスタンスがそれぞれ独立した状態を持ちます。フィールド宣言とメソッド宣言がクラス本体に何をもたらすかを定めているのは、[Java言語仕様（SE 21版）第8章 Classes](https://docs.oracle.com/javase/specs/jls/se21/html/jls-8.html)です。たとえば注文を表す `Order` クラスは、金額や明細というフィールドと、合計を計算する `calcTotal` というメソッドを一緒に持ちます。データと計算ロジックが同居するため、「金額の計算規則」を変えたいときに触る場所は1クラスに収まる形です。この凝集こそが、規模が大きい業務システムで効いてきます。

## privateとゲッターでカプセル化を実装する手順とアクセス4段階

カプセル化は三本柱の土台です。オブジェクトの内部フィールドを外から直接触らせず、公開するメソッド経由でしか読み書きできないようにして、データが不正な状態になる経路を塞ぎます。

### アクセス修飾子で公開範囲を絞り込むカプセル化の具体的な実装手順

Javaではフィールドを `private` で宣言し、外部からの参照や更新を `public` なメソッド（ゲッター・セッター）に限定します。[Oracleの公式チュートリアル「Controlling Access to Members of a Class」](https://docs.oracle.com/javase/tutorial/java/javaOO/accesscontrol.html)では、メンバーレベルの公開範囲を `public`・`protected`・修飾子なし（package-private）・`private` の4段階として表で示しています。よく「3段階」と説明されますが、修飾子を省いたときの既定はpackage-private（同一パッケージ内だけ）です。`protected` は同一パッケージと別パッケージのサブクラスに見える範囲で、継承前提の設計でしか使いません。この4段階で「誰が触れてよいか」を型として固定できます。

### 残高クラスで不変条件を守るJavaコードの書き方と検証の置き場所

カプセル化の勘所は、更新メソッドの内側に検証を置ける点です。下のコードは残高を `private` にし、負の金額と残高不足の経路をメソッド内で塞ぎました。この不変条件を全呼び出し箇所で守らせる保証は、フィールドを公開した設計では作れません。

```
public class Account {
    private long balance;              // 外部から直接は触らせない

    public Account(long initial) {
        if (initial < 0) {
            throw new IllegalArgumentException("初期残高が負");
        }
        this.balance = initial;
    }

    public long balance() {            // 読み取りだけ公開する
        return balance;
    }

    public void withdraw(long amount) {
        if (amount <= 0) {
            throw new IllegalArgumentException("金額が不正");
        }
        if (amount > balance) {
            throw new IllegalStateException("残高不足");
        }
        balance -= amount;             // 不変条件はこの1箇所だけで守る
    }
}
```

### 情報隠蔽が内部実装の変更コストを下げる仕組みと実装判断の目安

カプセル化の実利は、公開する窓口を狭くするほど内部実装を自由に差し替えられることにあります。外部が触るのがメソッドの入出力だけなら、内部のデータ構造をリストからマップへ変えても、呼び出し側は影響を受けません。逆に、フィールドをそのまま外部へ公開した設計は、内部構造の変更がそのまま外部の修正に波及します。実装判断の目安は単純で、「そのフィールドを外から書き換える正当な理由があるか」を問い、無ければ `private` にして読み取り専用にするのが定石です。カプセル化の詳細な実装例やアクセス修飾子の使い分けは、[カプセル化とは何か？オブジェクト指向における基本概念を解説](https://www.issoh.co.jp/tech/details/6001/)でコードとともに掘り下げています。

### Java・C#・Pythonで公開範囲の指定方法がどう違うかを比べる

言語ごとに道具の粒度は違います。[Microsoft LearnのC#解説「Overview of object oriented techniques in C#」](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/object-oriented/)によれば、C#のアクセス修飾子は `public`・`protected`・`internal`・`protected internal`・`private`・`private protected` の6種類で、既定は `private` です。`internal` がある分、Javaのパッケージ単位より広い境界を引けます。

一方、[Python公式チュートリアル9章「Classes」](https://docs.python.org/3/tutorial/classes.html)は、外部からアクセスできない私的なインスタンス変数はPythonに存在しないと明記しています。`_spam` を非公開扱いにする慣習と、`__spam` を `_クラス名__spam` へ書き換える名前修飾があるだけで、隠蔽は言語の強制ではなく合意で成り立つ形です。

## extendsで継承を書く手順とis-a関係を判定するチェック項目

継承は、既存クラスの状態と振る舞いを引き継いで新しいクラスを作る仕組みです。共通部分を親にまとめれば重複コードは減りますが、使いどころを誤ると保守を難しくする副作用が最も出やすい要素でもあります。

### 継承の実装方法と、is-a関係が成立するかどうかの見極め基準

Javaでは `extends` で親クラスを指定し、子クラスは親のフィールドとメソッドを引き継ぎます。継承を使ってよいのは、子が親の一種だと言い切れる is-a 関係のときだけです。「正社員は従業員の一種」なら `Employee` を親に置けます。一方、「注文は顧客が持つもの」という has-a の関係を継承で表すのは誤りで、これは合成（フィールドとして持つ）で表現します。判断のコツは、「子は親として扱っても破綻しないか」を確認することです。親を受け取る処理に子を渡して意味が通るなら継承が成立します。委譲との切り分けは[継承とは？オブジェクト指向での使いどころ・委譲との使い分け・アンチパターン](https://www.issoh.co.jp/tech/details/2724/)で個別に扱いました。

### 継承の乱用がコードの密結合を招く、典型的な失敗パターンと兆候

継承の落とし穴は、親の実装変更が子へ予期せず波及する密結合です。コードを再利用したいだけの理由で無関係なクラスを継承すると、親のメソッドを1つ直しただけで、それを暗黙に前提にしていた複数の子が同時に壊れます。継承の階層が3段4段と深くなるほど、あるメソッドが最終的にどう振る舞うかを追うコストが上がる点も見落としがちです。実装では「共通化のために継承したくなったら、まず合成で置き換えられないか」を先に検討する習慣が有効に働きます。深い階層を解きほぐす手順は[リファクタリングとは？24の観点とタイミング・進め方](https://www.issoh.co.jp/column/details/2836/)にあります。切り分けの基準は後半の独自章にまとめました。

## interfaceでポリモーフィズムを実装し条件分岐を置き換える手順

ポリモーフィズムは、同じ呼び出し方で異なる振る舞いを実行できる仕組みです。呼び出し側のコードを変えずに、渡すオブジェクトの型によって処理を切り替えられるため、拡張に強い設計の要になります。

### メソッドの再定義とインタフェースによる振る舞いの切り替えの実装

実現手段は主に2つです。1つはメソッドのオーバーライドで、親のメソッドを子で `@Override` を付けて再定義します。もう1つはインタフェースで、`interface` に共通の窓口だけを宣言し、複数のクラスが `implements` でそれぞれの実装を持つ形です。[Oracleの公式チュートリアル「Classes and Objects」](https://docs.oracle.com/javase/tutorial/java/javaOO/index.html)もクラス・継承・インタフェースをこの順で扱う構成です。インタフェースという語そのものが何を指し、コード上の契約とシステム間の契約がどうつながるかは[インターフェースとは？意味と種類・コードと通信の契約](https://www.issoh.co.jp/tech/details/17395/)で扱っています。

### Notifierインタフェースで通知手段を差し替えるコードの全体像

下のコードは「通知を送る」窓口を `Notifier` として切り出し、メール送信とチャット送信を別クラスで実装した例です。呼び出し側は `Notifier` 型しか知らないので、送信手段が増えても増えるのは実装クラスだけです。

```
public interface Notifier {
    void send(String to, String body);
}

public class MailNotifier implements Notifier {
    @Override
    public void send(String to, String body) {
        System.out.println("mail:" + to + " body:" + body);
    }
}

public class ChatNotifier implements Notifier {
    @Override
    public void send(String to, String body) {
        System.out.println("chat:" + to + " body:" + body);
    }
}

public class OrderService {
    private final Notifier notifier;   // 実装ではなく窓口だけを持つ

    public OrderService(Notifier notifier) {
        this.notifier = notifier;
    }

    public void complete(String userId) {
        notifier.send(userId, "注文が確定しました");
    }
}
```

テスト時は送信内容を記録するだけの実装を渡せば、メールサーバーを立てずに検証できます。多態性の種類は[ポリモフィズム（多態性）とは？読み方・種類・Javaの実装をわかりやすく解説](https://www.issoh.co.jp/tech/details/2614/)にまとめました。

### 型ごとの長い条件分岐をポリモーフィズムへ置き換える判断の基準

ポリモーフィズムが効くのは、型ごとに処理を分ける長い条件分岐が出てきた場面です。種別を見て振る舞いを切り替える `switch` や `if` の連鎖は、種別が増えるたびに全箇所へ分岐を足す必要が生じます。これをインタフェースと実装クラスへ置き換えると、新しい種別はクラスを1つ追加するだけで済み、既存コードを触りません。ただし種別が2つで今後も増えないと分かっているなら、無理に抽象化せず条件分岐のままにする判断も妥当です。この置き換えはStrategyやFactoryとして[GoFのデザインパターンとは？23種類の一覧と代表パターンをコードで解説](https://www.issoh.co.jp/tech/details/2615/)に定石化されています。

## 手続き型・関数型・データ指向とオブジェクト指向の使い分けの判断軸

オブジェクト指向はあらゆる場面で最善の設計とは限りません。手続き型・関数型・データ指向にはそれぞれ得意な領域があり、扱うデータの性質と変更の入り方で選び分けます。

### 各パラダイムの得意領域を一覧で比べる設計アプローチ選択の指針

下表は、状態の扱い・変更に強い箇所・向く規模という観点で4つの考え方を比べたものです。オブジェクト指向が効くのは、状態を持つ実体が多く、型の追加という形で機能が増えていく中〜大規模の業務システムです。大量データを一括で流し込んで変換する処理が中心なら、データ側の構造を主役に据えるデータ指向のほうが素直に書けます。

| 観点      | 手続き型   | オブジェクト指向   | データ指向    |
| ------- | ------ | ---------- | -------- |
| 状態の扱い   | 各所に散在  | オブジェクトに凝集  | データを一括処理 |
| 変更に強い箇所 | 処理の追加  | 型・種別の追加    | スキーマの設計  |
| 向く規模    | 小規模な処理 | 中〜大の業務システム | 大量データ基盤  |
| 学習コスト   | 低い     | 中程度        | やや高い     |

データ中心にシステムを設計する考え方は、オブジェクト指向と対になる発想です。分散データや大量処理を前提にした設計の基本原則は、[データ指向アプリケーションデザインの基本原則と利点](https://www.issoh.co.jp/column/details/2967/)で扱っています。どちらか一方だけが正解ではなく、扱う対象で選び分けるのが実務の姿勢です。業務ルールをオブジェクト側に寄せ切る設計は[ドメイン駆動設計（DDD）とは？意味・基本用語・実装と採用判断をわかりやすく解説](https://www.issoh.co.jp/tech/details/3580/)が続きです。

## 継承より合成を第一候補にする実装判断の基準と、見送るべき場面

ここは競合記事が手薄な独自の論点です。結論を先に言い切ると、コードの再利用が目的なら継承ではなく合成（コンポジション）を第一候補にします。継承を選ぶのは、子が親の一種だと型として言い切れる is-a 関係が成立する場合に限定します。

### 合成を第一候補にする理由と、継承を採用してよい2つの前提条件

合成は、別クラスのインスタンスをフィールドとして持ち、必要な機能をそのオブジェクトへ委譲する書き方です。継承と違い親子の型が縛られないため、持つ相手を後から差し替えられ、テスト時にはモックへ置き換えるのも容易になります。継承を採用してよい条件は2つです。第一に、親を受け取る処理へ子を渡しても意味が破綻しない（型の置換が成り立つ）こと。第二に、親の変更を子へ確実に波及させたい設計意図があること。この2つを満たさない再利用目的の継承は、合成へ置き換えます。依存の向きを固定する議論は[クリーンアーキテクチャとは？4層構造・SOLID原則・依存性逆転を実装目線で解説](https://www.issoh.co.jp/column/details/3406/)へ続きます。

### 継承をやめて委譲へ書き換える前後のコードと差し替え点の確認手順

同じクラスを継承版と委譲版で並べます。前者は共通メソッドを使いたいだけで `extends` しており、親の `join` の仕様が変わると黙って壊れます。後者は結合する部品を引数で受け取る形です。

```
// 悪い例: 再利用目的の継承（親の変更が子を壊す）
class CsvExporter extends StringUtil {
    String export(List<Row> rows) {
        return join(rows);             // 親の join に暗黙依存
    }
}

// 良い例: 委譲へ書き換える（持つ相手を差し替えられる）
class CsvExporter {
    private final Joiner joiner;

    CsvExporter(Joiner joiner) {
        this.joiner = joiner;
    }

    String export(List<Row> rows) {
        return joiner.join(rows);
    }
}
```

差し替え点は3つです。`extends` を消す、親メソッドをフィールド経由の呼び出しに変える、コンストラクタで依存を受け取る。階層が深い場合は末端の子から順に移します。

### 継承を見送るべき具体的な失敗場面と、合成へ切り替える判断基準

継承を見送るべき場面を条件付きで挙げます。まず、共通のメソッドを使いたいだけで型の親子関係に必然性がないとき。この場合の継承は、親の些細な修正で無関係な子が壊れる密結合を生むため採用しません。次に、階層がすでに2段あり、そこへさらに継承を重ねようとしているとき。深い継承は振る舞いの追跡コストが跳ね上がるので、合成か、共通処理を別クラスへ切り出す設計に切り替えます。逆に、フレームワークが基底クラスの継承を前提にしている場合は、その作法に従って継承を使うのが自然です。「継承は禁止」ではなく、「再利用目的では選ばない」という線引きが実務では機能します。

## recordとsealedで型を絞る書き方とswitch網羅の実装手順

三本柱の説明は1990年代の言語仕様を前提に書かれたものが多い一方、現在のJavaにはクラス設計を短く安全に書ける機能があります。実装で使う2つを正式化された版と押さえます。

### recordで値の入れ物を宣言しボイラープレートを削る書き方

[OpenJDKのJEP 395「Records」](https://openjdk.org/jeps/395)はステータスがClosed／Delivered、Releaseが16と記載されており、JDK 16で正式な言語機能になりました。`record` はフィールド・コンストラクタ・アクセサ・`equals`・`hashCode`・`toString` を宣言1行から生成するため、値を運ぶクラスの定型コードが消えます。コンパクトコンストラクタに検証を置けば、不正な値のインスタンスは存在しなくなります。`record` の制限や自動生成の詳細は[レコードクラスとは？Javaのrecordの使い方・自動生成・制限を実装者向けに解説](https://www.issoh.co.jp/tech/details/3254/)で扱いました。

### sealedで実装クラスを列挙しswitchの網羅性を型で担保する

[OpenJDKのJEP 409「Sealed Classes」](https://openjdk.org/jeps/409)も同じくClosed／Deliveredで、Releaseは17です。`sealed` を付けたインタフェースは `permits` に書いた型からしか実装されないため、コンパイラは実装候補を打ち止めとして扱います。結果として `switch` でその型を分岐するとき、書き漏らした枝はコンパイルエラーとして表に出る形です。分岐をポリモーフィズムで消す設計と、sealedで網羅させる設計は対立せず、種別が固定なら後者を選びます。

```
// JDK 16で正式化された record（JEP 395）
public record Money(String currency, long amount) {
    public Money {
        if (amount < 0) {
            throw new IllegalArgumentException("負の金額");
        }
    }
}

// JDK 17で正式化された sealed（JEP 409）
public sealed interface Payment permits Card, Bank, Store {}

public record Card(Money total, String brand) implements Payment {}
public record Bank(Money total, String bankCode) implements Payment {}
public record Store(Money total, String storeCode) implements Payment {}

// permits の型を網羅していないと switch がコンパイルエラーになる
static String label(Payment p) {
    return switch (p) {
        case Card c  -> "カード:" + c.brand();
        case Bank b  -> "銀行振込:" + b.bankCode();
        case Store s -> "コンビニ:" + s.storeCode();
    };
}
```

決済手段を1つ足したとき、直すべき箇所はコンパイラが列挙します。`permits` の書き方と `final`・`non-sealed` の指定は[Javaのシールクラス（sealed class）とは？permitsとfinal/sealed/non-sealed・switch網羅の使い方](https://www.issoh.co.jp/tech/details/3219/)に詳しくあります。

## 受託開発と保守運用でオブジェクト指向の設計が投資に見合う条件

オブジェクト指向は、作って終わりではなく長く保守するシステムでこそ投資が回収されます。設計に手をかける価値があるかは、システムの寿命と変更頻度で見極めます。

### 保守フェーズで設計品質がシステムの総保有コストに跳ね返る仕組み

業務システムの費用は開発時より運用・改修フェーズで積み上がります。カプセル化で内部を隠し、ポリモーフィズムで拡張点を用意しておくと、機能追加のたびに既存コードへ手を入れる範囲が狭まり、改修時の影響調査とテストの工数が下がる構造です。逆に、状態が各所へ漏れ、種別分岐がコード全体に散った設計は、1つの変更が広範囲へ波及し、改修のたびに回帰テストの範囲が膨らみます。設計品質は、初期費用ではなく総保有コストで評価するのが妥当です。一創では、既存システムの改修・保守や社内での内製化を支える[保守運用・内製化支援](https://www.issoh.co.jp/service/system/maintenance/)を通じて、こうした設計の見直しから開発体制づくりまで伴走しています。

### 過剰な抽象化を避け、設計への投資を見合わせる判断の目安と具体例

一方で、オブジェクト指向の抽象化を効かせすぎると、読むために追う階層が増えて逆に保守しにくくなります。使い捨てのスクリプトや、仕様が固まっていて拡張予定のない小さな処理では、インタフェースや継承階層を作り込む価値は薄く、素直な手続き型で書くほうが早く安く仕上がります。抽象化は将来の変更に備える保険であり、変更が来ないと分かっているところに保険料は払いません。拡張点を用意するのは、変更が現実に予想される箇所へ絞ります。

## よくある質問

オブジェクト指向でつまずきやすい点を、検索されやすい質問に沿って簡潔に答えます。

### オブジェクト指向の三大要素とは何ですか？

カプセル化・継承・ポリモーフィズムの3つです。カプセル化は内部状態を隠してデータの整合性を守り、継承は共通コードを親クラスにまとめて重複を減らし、ポリモーフィズムは呼び出し側を変えずに振る舞いを差し替えます。三者は役割が分かれており、実装ではまずカプセル化で公開範囲を絞ることから始めると効果を実感しやすいはずです。抽象化を加えて4本柱と数える整理もあり、C#の公式ドキュメントはカプセル化を「第一の柱」と呼んでいます。

### オブジェクト指向と手続き型の違いは何ですか？

データと処理の置き場所が違います。手続き型はデータをグローバルに置き、関数が外から書き換えます。オブジェクト指向はデータと処理を同じオブジェクトへ閉じ込め、外部からは公開メソッド経由でしか触れません。この違いにより、規模が大きくなったとき変更の影響範囲をオブジェクト内部に限定できます。

### 継承と合成（コンポジション）はどちらを使うべきですか？

再利用が目的なら合成を第一候補にします。継承を選ぶのは、子が親の一種だと型として言い切れる is-a 関係が成立する場合です。再利用したいだけで無関係なクラスを継承すると、親の変更が子を壊す密結合を生みます。合成は持つ相手を後から差し替えやすく、テストもしやすい利点があります。

### ポリモーフィズムはどんな場面で使いますか？

型ごとに処理を分ける条件分岐が長くなってきた場面です。種別を見て振る舞いを切り替える分岐を、インタフェースと実装クラスへ置き換えると、新しい種別はクラスを1つ足すだけで済み、既存コードを触りません。種別が固定なら、sealedで列挙して `switch` の網羅をコンパイラに検査させる書き方も選べます。

### オブジェクト指向はどの言語で学ぶとよいですか？

アクセス修飾子や型の宣言が明示的なJavaやC#は、三本柱の概念と実装の対応を確認しやすい言語です。PythonやRubyでも同じ概念を学べますが、Python公式ドキュメントが「私的なインスタンス変数は存在しない」と書いているとおり隠蔽が慣習に依存するため、まず明示的な言語で仕組みを押さえてから動的な言語へ移ると、隠蔽や型の置換の意味が腹落ちしやすくなります。

## 関連記事

- [カプセル化とは何か？オブジェクト指向における基本概念を解説](https://www.issoh.co.jp/tech/details/6001/)：アクセス修飾子と実装例まで掘り下げた記事です。
- [継承とは？オブジェクト指向での使いどころ・委譲との使い分け・アンチパターン](https://www.issoh.co.jp/tech/details/2724/)：継承の採用条件と委譲への切り替えの整理です。
- [ポリモフィズム（多態性）とは？読み方・種類・Javaの実装をわかりやすく解説](https://www.issoh.co.jp/tech/details/2614/)：多態性の種類とJavaでの実装を確認できます。
- [クリーンアーキテクチャとは？4層構造・SOLID原則・依存性逆転を実装目線で解説](https://www.issoh.co.jp/column/details/3406/)：層と依存の向きまで広げる設計の記事です。
- [データ指向アプリケーションデザインの基本原則と利点](https://www.issoh.co.jp/column/details/2967/)：オブジェクト指向と対になるデータ中心の設計を解説した記事です。

---

出典: [オブジェクト指向とは？三本柱の実装手順とrecord・sealedまでJavaコードで解説](<https://www.issoh.co.jp/tech/details/13412/>)（株式会社一創）
