プログラミング言語の一つ、Javaを学習していると、オブジェクト指向プログラミング(OOP)という言葉をよく耳にします。
しかし、このオブジェクト指向プログラミングとは一体何なのでしょうか?
本記事では、Javaにおけるオブジェクト指向プログラミングの基本的な概念や特徴をわかりやすく解説します!

- 現役のフルスタックエンジニアとして活躍中
- 開発チームリーダーとして複数プロジェクトをリード
- 副業プログラミングスクール講師として数百名以上を指導してきた教育のプロ
- プログラミングスクールのカリキュラム執筆経験あり
オブジェクト指向プログラミングとは
オブジェクト指向プログラミングは、効率的でメンテナンス性の高いプログラムを作成するための重要な考え方です。
特にJavaは完全なオブジェクト指向言語として知られています。
- プログラムを「オブジェクト」と呼ばれる部品の集まりとして設計・実装する
- 各オブジェクトはデータ(属性)と機能(メソッド)を持つ
- 効率的な開発とコードの再利用性を高める
オブジェクト指向の3つの特徴
| 特徴 | 説明 | 
|---|---|
| カプセル化 | データと機能をひとまとめにし、外部からの不適切なアクセスを防ぐ | 
| 継承 | 既存のクラスを基に新しいクラスを作成し、コードの再利用性を高める | 
| ポリモーフィズム | 同じインターフェースで異なる動作を実現する | 

Javaにおけるクラスとオブジェクト
Javaでは、クラスがオブジェクト指向プログラミングの中心的な概念となります。
- クラス:オブジェクトの設計図
- オブジェクト:クラスの実体(インスタンス)
クラスの定義
Javaでクラスを定義する基本的な構文を見てみましょう。
public class Car {
    // フィールド(属性)
    private String model;
    private int year;
    // コンストラクタ
    public Car(String model, int year) {
        this.model = model;
        this.year = year;
    }
    // メソッド
    public void startEngine() {
        System.out.println("エンジンを始動します");
    }
    // getter, setterメソッド
    public String getModel() {
        return model;
    }
    public void setModel(String model) {
        this.model = model;
    }
}この例では、Carクラスを定義しています。
- modelとyearはクラスのフィールド(属性)
- startEngine()はメソッド(機能)
- コンストラクタはオブジェクトを初期化するために使用
オブジェクトの作成と使用
クラスからオブジェクトを作成し、使用する例を見てみましょう。
public class Main {
    public static void main(String[] args) {
        // オブジェクトの作成
        Car myCar = new Car("Toyota", 2023);
        // メソッドの呼び出し
        myCar.startEngine();
        // getterメソッドを使用してデータにアクセス
        System.out.println("車種: " + myCar.getModel());
    }
}この例では、Carクラスのオブジェクトを作成し、そのメソッドを呼び出しています。

カプセル化
カプセル化は、データ(フィールド)と、そのデータを操作するメソッドをひとまとめにし、外部からの直接アクセスを制限する概念です。
Javaでは、アクセス修飾子を使用してカプセル化を実現します。
| アクセス修飾子 | アクセス範囲 | 
|---|---|
| private | 同じクラス内からのみアクセス可能 | 
| protected | 同じパッケージ内、または継承したクラスからアクセス可能 | 
| public | どこからでもアクセス可能 | 
| (デフォルト) | 同じパッケージ内からのみアクセス可能 | 
通常、フィールドはprivateとし、必要に応じてpublicなgetter/setterメソッドを通じてアクセスします。これにより、データの整合性を保ちつつ、柔軟な操作が可能になります。
継承
継承は既存のクラス(親クラス)の特性を新しいクラス(子クラス)に引き継ぐ機能です。
Javaでは extends キーワードを使用して継承を実現します。
public class ElectricCar extends Car {
    private int batteryCapacity;
    public ElectricCar(String model, int year, int batteryCapacity) {
        super(model, year);  // 親クラスのコンストラクタを呼び出し
        this.batteryCapacity = batteryCapacity;
    }
    // オーバーライド
    @Override
    public void startEngine() {
        System.out.println("電気モーターを始動します");
    }
    // 新しいメソッド
    public void charge() {
        System.out.println("バッテリーを充電中...");
    }
}この例では、ElectricCarクラスがCarクラスを継承しています。
- 親クラスのすべての特性を引き継ぐ
- 新しいフィールド(batteryCapacity)とメソッド(charge())を追加
- startEngine()メソッドをオーバーライドして、電気自動車特有の動作を実装
ポリモーフィズム
ポリモーフィズムは、同じインターフェースを持つ異なるクラスのオブジェクトを、統一した方法で扱える機能です。
Javaでは主に2つの方法でポリモーフィズムを実現します:
1. メソッドのオーバーライド
子クラスで親クラスのメソッドを再定義することで、同じメソッド名でも異なる動作を実現できます。
2. インターフェース
インターフェースを使用すると、異なるクラスに共通のメソッドを定義できます。
interface Vehicle {
    void move();
}
class Car implements Vehicle {
    public void move() {
        System.out.println("車が走ります");
    }
}
class Bicycle implements Vehicle {
    public void move() {
        System.out.println("自転車が走ります");
    }
}
public class Main {
    public static void main(String[] args) {
        Vehicle car = new Car();
        Vehicle bicycle = new Bicycle();
        car.move();     // 出力: 車が走ります
        bicycle.move(); // 出力: 自転車が走ります
    }
}この例では、CarとBicycleクラスが共通のVehicleインターフェースを実装しています。これにより、異なるクラスのオブジェクトを同じ型(Vehicle)として扱うことができます。
抽象クラスとインターフェース
抽象クラスとインターフェースは、共通の機能を定義するためのJavaの機能です。
抽象クラス
抽象クラスは、直接インスタンス化できないクラスで、共通の機能を持つ複数のクラスの基底クラスとして使用されます。
abstract class Animal {
    abstract void makeSound();
    public void sleep() {
        System.out.println("Zzzz");
    }
}
class Dog extends Animal {
    void makeSound() {
        System.out.println("ワン!");
    }
}
class Cat extends Animal {
    void makeSound() {
        System.out.println("ニャー!");
    }
}インターフェース
インターフェースは、クラスが実装すべきメソッドの仕様を定義します。複数のインターフェースを実装することができます。
interface Flyable {
    void fly();
}
interface Swimmable {
    void swim();
}
class Duck implements Flyable, Swimmable {
    public void fly() {
        System.out.println("アヒルが飛びます");
    }
    public void swim() {
        System.out.println("アヒルが泳ぎます");
    }
}抽象クラスとインターフェースの主な違い:
- 抽象クラスは一部の実装を持てるが、インターフェースは基本的にメソッドの宣言のみ
- クラスは1つの抽象クラスしか継承できないが、複数のインターフェースを実装可能
- 抽象クラスはis-a関係、インターフェースはcan-do関係を表現するのに適している
例外処理
例外処理は、プログラムの実行中に発生する予期しないエラーを適切に処理するための機能です。Javaでは try-catch-finally ブロックを使用して例外を処理します。
public class ExceptionExample {
    public static void main(String[] args) {
        try {
            int result = divide(10, 0);
            System.out.println("結果: " + result);
        } catch (ArithmeticException e) {
            System.out.println("エラー: " + e.getMessage());
        } finally {
            System.out.println("処理が完了しました");
        }
    }
    public static int divide(int a, int b) throws ArithmeticException {
        if (b == 0) {
            throw new ArithmeticException("0で除算はできません");
        }
        return a / b;
    }
}この例では、0による除算を試みた際に発生する例外を適切に処理しています。
- tryブロック:例外が発生する可能性のあるコードを囲む
- catchブロック:特定の例外をキャッチして処理する
- finallyブロック:例外の有無に関わらず必ず実行される
ジェネリクス
ジェネリクスは、型安全性を保ちながら再利用可能なコードを書くための機能です。コンパイル時に型チェックを行うことで、実行時のエラーを防ぎます。
public class Box {
    private T content;
    public void set(T content) {
        this.content = content;
    }
    public T get() {
        return content;
    }
}
public class GenericExample {
    public static void main(String[] args) {
        Box intBox = new Box<>();
        intBox.set(10);
        System.out.println(intBox.get());
        Box stringBox = new Box<>();
        stringBox.set("Hello, Generics!");
        System.out.println(stringBox.get());
    }
}この例では、Boxクラスをジェネリックにすることで、異なる型のオブジェクトを格納できる汎用的なコンテナを作成しています。
コレクションフレームワーク
Javaのコレクションフレームワークは、データの格納と操作を効率的に行うためのクラスとインターフェースの集合です。主要なインターフェースには List, Set, Map があります。
import java.util.*;
public class CollectionExample {
    public static void main(String[] args) {
        // Listの例
        List list = new ArrayList<>();
        list.add("Apple");
        list.add("Banana");
        list.add("Cherry");
        System.out.println("List: " + list);
        // Setの例
        Set set = new HashSet<>();
        set.add(1);
        set.add(2);
        set.add(2);  // 重複は無視される
        System.out.println("Set: " + set);
        // Mapの例
        Map map = new HashMap<>();
        map.put("Alice", 25);
        map.put("Bob", 30);
        map.put("Charlie", 35);
        System.out.println("Map: " + map);
    }
}| コレクション | 特徴 | 使用例 | 
|---|---|---|
| List | 順序付きコレクション | データの順序が重要な場合 | 
| Set | 重複を許さないコレクション | ユニークな値の集合が必要な場合 | 
| Map | キーと値のペアを格納 | キーを使って値を素早く検索したい場合 | 

まとめ
この記事では、Javaにおけるオブジェクト指向プログラミングの基本概念から応用までを解説しました。
- クラスとオブジェクト、カプセル化、継承、ポリモーフィズムなどの基本的な概念
- 抽象クラス、インターフェース、例外処理、ジェネリクス、コレクションフレームワークなどの高度なトピック
- オブジェクト指向プログラミングの原則を理解し、適切に活用することで、保守性が高く、拡張性のあるコードを書くことができる
オブジェクト指向プログラミングの習得には時間と練習が必要ですが、その原則を理解し適切に応用することで、より良いソフトウェア設計と開発が可能になります。継続的な学習と実践を通じて、Javaプログラミングのスキルを磨いていくことをお勧めします。
