Java 入門

Java クラスとオブジェクト

オブジェクト指向プログラミング(OOP)は現代のソフトウェア開発の基礎であり、「クラス(Classes)」と「オブジェクト(Objects)」を理解することは、Java における OOP 原則をマスターするための土台となります。

本章では、クラスとオブジェクトのコア概念を紹介し、それらがどのようにオブジェクト指向プログラムのビルディングブロックを構成しているかを解説します。クラスの定義方法、それらのクラスからオブジェクトを生成する方法、そして両者の関係性について探求していきましょう。これらの知識は、後続の章で扱うより高度な OOP 概念の基礎となります。

1. クラスの定義

クラスは、オブジェクトを生成するための「設計図(Blueprint)」または「テンプレート」です。それは、そのクラスのオブジェクトが備えるべき特徴(属性)と振る舞い(メソッド)を定義します。クラスを「クッキーの型」と考えるなら、オブジェクトはその型から作られた「クッキー」そのものです。型が形状を決定し、個々のクッキーはその形状の「インスタンス(Instance)」となります。

1.1 クラスの構文

Java でクラスを定義するには、class キーワードを使用し、その後にクラス名を記述し、クラスの本体を波括弧 {} で囲みます。

class Dog {
    // 属性(フィールド)
    String breed; // 品種
    String name;  // 名前
    int age;      // 年齢

    // メソッド(振る舞い)
    void bark() {
        System.out.println("ワン!");
    }

    void displayDetails() {
        System.out.println("品種: " + breed + ", 名前: " + name + ", 年齢: " + age);
    }
}
  • class Dog: Dog という名前のクラスを宣言しています。Java では、クラス名は通常、大文字で始めます。
  • String breed;, String name;, int age;: これらは Dog クラスのプロパティ(またはフィールド)です。各 Dog オブジェクトが保持するデータを定義します。
  • void bark() { ... }, void displayDetails() { ... }: これらは Dog クラスのメソッドです。Dog オブジェクトが実行できるアクションを定義します。

2. フィールド(属性)とメソッド(振る舞い)

2.1 フィールド(フィールド)

フィールド(プロパティ、またはインスタンス変数とも呼ばれます)は、オブジェクトに関連付けられたデータを表します。これらはオブジェクトの「状態」を定義します。一つのクラスから生成された各オブジェクトは、それぞれ独立したプロパティのコピーを持ちます。

: Dog クラスにおいて、breed(品種)、name(名前)、age(年齢)がフィールドです。それぞれの Dog オブジェクトは、自分自身の品種、名前、年齢を持つことになります。

2.2 メソッド(振る舞い)

メソッドは、オブジェクトが実行できる操作や振る舞いを定義します。メソッドはオブジェクトのデータ(フィールド)を操作したり、他のオブジェクトと対話したりすることができます。

: Dog クラスにおいて、bark()(吠える)と displayDetails()(詳細を表示)がメソッドです。Dog オブジェクトは吠えるというアクションを実行したり、自分自身の詳細情報を表示したりできます。

3. オブジェクトの生成

オブジェクトはクラスの一つのインスタンスです。それはメモリ上に存在する具体的なエンティティであり、独自の状態(データ)と振る舞い(メソッド)を持ちます。オブジェクトを生成するプロセスは「インスタンス化(Instantiation)」とも呼ばれます。

3.1 オブジェクト生成の構文

Java では、new キーワードを使用してオブジェクトを生成し、その後にクラス名と括弧 () を記述します。これにより、そのクラスのコンストラクタが呼び出されます。

Dog myDog = new Dog();
  • Dog myDog: Dog 型の myDog という名前の変数を宣言します。この変数は、Dog オブジェクトへの参照を保持します。
  • new Dog(): メモリ上に新しい Dog オブジェクトを生成します。new キーワードはオブジェクトにメモリを割り当てる役割を担い、Dog()Dog クラスのコンストラクタを呼び出します。
  • =: 新しく生成された Dog オブジェクトの参照を myDog 変数に代入します。

3.2 フィールドとメソッドへのアクセス

オブジェクトの生成が完了すると、ドット演算子(.)を使用して、そのプロパティやメソッドにアクセスできるようになります。

myDog.breed = "ゴールデンレトリバー";
myDog.name = "バディ";
myDog.age = 3;

myDog.bark(); // 出力: ワン!
myDog.displayDetails(); // 出力: 品種: ゴールデンレトリバー, 名前: バディ, 年齢: 3
  • myDog.breed = "ゴールデンレトリバー";: myDog オブジェクトの breed フィールドに「ゴールデンレトリバー」を設定します。
  • myDog.bark();: myDog オブジェクトの bark() メソッドを呼び出します。このメソッドはコンソールに「ワン!」と表示します。

4. フィールドとメソッドの深い理解

より多くの例を通じて、フィールドとメソッドを深く掘り下げてみましょう。

4.1 フィールドとデータカプセル化

理想的には、フィールドは「カプセル化」されるべきです。つまり、クラスの外部から直接アクセスすることを制限すべきです。これは private(非公開)、protected(保護)、public(公開)といったアクセス修飾子(Access Modifiers)を使用して実現します。ここではまず publicprivate に注目します。

  • public: public と宣言されたプロパティは、どこからでもアクセス可能です。
  • private: private と宣言されたプロパティは、そのクラス自身の内部からしかアクセスできません。
class Car {
    public String model; // 公開プロパティ
    private int speed;   // 非公開プロパティ

    // 公開メソッド:速度を設定する(セッター)
    public void setSpeed(int speed) {
        if (speed >= 0 && speed <= 200) { // バリデーションロジックの例
            this.speed = speed;
        } else {
            System.out.println("無効な速度です。");
        }
    }

    // 公開メソッド:速度を取得する(ゲッター)
    public int getSpeed() {
        return speed;
    }
}

public class Main {
    public static void main(String[] args) {
        Car myCar = new Car();
        myCar.model = "Tesla Model 3"; // 許可:publicプロパティへのアクセス

        // myCar.speed = 100; // エラー:speedがpublicなら可能だが、privateなので直接アクセス不可

        myCar.setSpeed(100); // 許可:publicメソッドを通じてspeedへアクセス
        System.out.println("車の速度: " + myCar.getSpeed());
    }
}

この例では:

  • modelpublic なので、Main クラスから直接アクセスできます。
  • speedprivate なので、setSpeed()getSpeed() メソッドを通じてのみアクセス可能です。これが「カプセル化(Encapsulation)」の一例であり、データを保護し、データへのアクセスや修正方法を制御することを可能にします。

4.2 メソッドと振る舞いの実装

メソッドには、オブジェクトの振る舞いを定義するロジックが含まれます。メソッドは引数(入力)を受け取り、戻り値(出力)を返すことができます。

class Rectangle {
    int width;
    int height;

    // 面積を計算するメソッド。整数を返す
    int calculateArea() {
        return width * height;
    }

    // サイズを設定するメソッド。2つの引数を受け取る
    void setDimensions(int width, int height) {
        this.width = width;
        this.height = height;
    }
}

public class Main {
    public static void main(String[] args) {
        Rectangle myRectangle = new Rectangle();
        myRectangle.setDimensions(5, 10);
        int area = myRectangle.calculateArea();
        System.out.println("長方形の面積: " + area); // 出力: 長方形の面積: 50
    }
}

5. コンストラクタ

コンストラクタ(Constructor)は、クラスのオブジェクトが生成されるときに自動的に呼び出される特殊なメソッドです。通常、オブジェクトのプロパティを初期化するために使用されます。コンストラクタの名前はクラス名と完全に一致させる必要があり、戻り値の型(void すら書かない)を持ちません。

5.1 デフォルトコンストラクタ

クラス内にコンストラクタを一つも定義しない場合、Java は自動的に「デフォルトコンストラクタ」を提供します。デフォルトコンストラクタは引数を受け取らず、すべてのフィールドをデフォルト値(例:int なら 0、String なら null)に初期化します。

5.2 引数付きコンストラクタ

特定の値を使用してオブジェクトのフィールドを初期化するために、独自の引数付きコンストラクタを定義することもできます。

class Person {
    String name;
    int age;

    // コンストラクタ
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    void displayDetails() {
        System.out.println("氏名: " + name + ", 年齢: " + age);
    }
}

public class Main {
    public static void main(String[] args) {
        Person john = new Person("田中太郎", 30); // 引数付きコンストラクタを使用
        john.displayDetails(); // 出力: 氏名: 田中太郎, 年齢: 30
    }
}

5.3 コンストラクタのオーバーロード

一つのクラスに、パラメータリストが異なる複数のコンストラクタを持たせることができます。これを「コンストラクタのオーバーロード(Constructor Overloading)」と呼びます。コンパイラは、オブジェクト生成時に渡された引数に基づいて、どのコンストラクタを呼び出すかを決定します。

class Book {
    String title;
    String author;
    String isbn;

    // コンストラクタ 1: タイトルと著者のみ
    public Book(String title, String author) {
        this.title = title;
        this.author = author;
        this.isbn = "不明"; // デフォルト値を付与
    }

    // コンストラクタ 2: タイトル、著者、ISBN
    public Book(String title, String author, String isbn) {
        this.title = title;
        this.author = author;
        this.isbn = isbn;
    }

    void displayDetails() {
        System.out.println("タイトル: " + title + ", 著者: " + author + ", ISBN: " + isbn);
    }
}

public class Main {
    public static void main(String[] args) {
        Book book1 = new Book("指輪物語", "J.R.R.トールキン");
        Book book2 = new Book("高慢と偏見", "ジェーン・オースティン", "978-0141439518");

        book1.displayDetails(); // 出力: タイトル: 指輪物語, 著者: J.R.R.トールキン, ISBN: 不明
        book2.displayDetails(); // 出力: タイトル: 高慢と偏見, 著者: ジェーン・オースティン, ISBN: 978-0141439518
    }
}

6. this キーワード

this キーワードは現在のオブジェクトへの参照です。主に以下のシナリオで使用されます。

  1. メソッドやコンストラクタの引数名がクラスのフィールド名と同じ場合に、オブジェクトのフィールドにアクセスする(名前の衝突を解決する)。
  2. 同じクラス内で、一つのコンストラクタから別のコンストラクタを呼び出す(高度な OOP トピックで詳解します)。
  3. メソッドから現在のオブジェクトを返す(基本的なシナリオでは稀です)。
class Employee {
    String name;
    int id;

    public Employee(String name, int id) {
        this.name = name; // 'this.name' はクラスのフィールドを指し、'name' は引数を指す
        this.id = id;     // 'this.id' はクラスのフィールドを指し、'id' は引数を指す
    }

    void displayDetails() {
        System.out.println("氏名: " + this.name + ", ID: " + this.id); // ここでの 'this' 使用は任意だが、良い習慣とされる
    }
}

public class Main {
    public static void main(String[] args) {
        Employee employee1 = new Employee("アリス・スミス", 12345);
        employee1.displayDetails(); // 出力: 氏名: アリス・スミス, ID: 12345
    }
}

この例では、this.name = name; と書くことで、Employee クラスの name プロパティとコンストラクタの引数 name を厳格に区別しています。this キーワードがなければ、コンパイラはどちらの name を指しているのか判断できません。