第03章 オブジェクト指向

03_oop

第03章 オブジェクト指向プログラミング

独学用の演習・段階別ヒント・自己確認: こちらから開く。各節を学んだあとに利用してください。

この章の読み方: 本文の基本説明、直後の発展解説、例題、演習の順に読んでください。専門用語は初めて出てきた箇所で意味を確認し、コードの予想結果を考えてから実行します。

章末の演習は、それまでの本文の内容を使って解けるように設計しています。

用語の表記: 全10章共通の用語集 と表記を揃えています。

対象:プログラミング未経験者・Javaを初めて学ぶ他言語経験者
前提:第01章の開発環境構築と第02章の変数・条件分岐・繰り返し・メソッドを学習済み
開発環境:Windows 11、JDK 21、Eclipse(ローカル環境)

この章の目標

これまでの学習では、主に main メソッドの中で変数を作り、条件によって処理を分け、計算結果を表示してきました。しかし、現場のシステムは数百から数千のファイルに分かれることもあり、すべての処理を一か所に書くことはできません。

そこで、関連するデータと操作をまとめる「クラス」という仕組みを使います。この章では、クラスの作成から複数クラスの連携までを、自分で説明し実装できるようになることを目指します。

学習を終えたら、次のことができるようになります。

  1. クラスとインスタンスの違いを、身近な例を使って説明できる。
  2. フィールドとメソッドを持つクラスを作れる。
  3. コンストラクタで初期状態を整え、this の意味を理解できる。
  4. private と公開メソッドを使い、不正な状態を防げる。
  5. 継承・オーバーライド・ポリモーフィズムの関係を説明できる。
  6. インターフェースを使った簡単な設計を実装できる。
  7. 既存クラスの振る舞いを変更する際に、変更箇所と確認項目を考えられる。

学び方とファイルの扱い

この章のサンプルは、節ごとに独立したプログラムです。examples/03_oop にある各フォルダー内のJavaファイルを参照してください。

同じ名前の Main.java が複数ありますが、別々のフォルダーに置いてあるため問題ありません。Eclipseでは各節につき新規Javaプロジェクトを作るか、節ごとに別のパッケージを使ってください。

実行手順は第01章を参照してください。

コマンドプロンプトで試す場合は、対象の節のフォルダーを開き、javac *.java に相当するWindowsのコマンド javac *.java を実行し、続けて java Main を実行します。

ターミナルの作業フォルダーが違うとファイルが見つからないので、まず dir でJavaファイルの存在を確認してください。


01. オブジェクト指向とは何か

「オブジェクト」は、プログラムが扱うひとまとまりの対象を表します。たとえば銀行口座をプログラムで扱うとき、口座名義や残高という情報だけでなく、入金する、出金するという操作も必要です。

関連する情報と操作を一つにまとめれば、ほかの処理から利用しやすくなります。

オブジェクト指向は、こうした対象を組み合わせてプログラムを作る考え方です。「すべてを現実の物に置き換える」という意味ではありません。業務システムでは、注文、商品、請求書、会員などをクラスとして表すことがあります。

一方、計算処理やサービスをクラスにまとめることもあります。

オブジェクト指向を学ぶ上で重要なのは、データをどこに持たせるか、そのデータを誰が変更してよいか、ほかのクラスに何を公開するかを意識することです。最初から難しい設計を行う必要はありません。

この章では口座や商品を題材に少しずつ考えます。

用語

  • クラス:オブジェクトの形や振る舞いを定義する設計図に相当するもの。
  • インスタンス:クラスをもとに作成された具体的なオブジェクト。通常、new を使って生成する。
  • フィールド:クラスの中に保持するデータ。main の中だけで使うローカル変数とは寿命や利用範囲が異なる。
  • メソッド:オブジェクトが提供する操作や処理。

日常的には「オブジェクト」と「インスタンス」をほぼ同じ意味で呼ぶ場合があります。この章では、クラスから作られた実体であることを強調するときに「インスタンス」と呼びます。


理解を深める:変数を個別に並べる方法では管理が難しくなる場面

商品の名前と値段を管理する場合、最初は String name1、int price1 のような変数で十分に見えます。しかし、商品が10件になれば、名前と価格の対応が崩れやすくなります。

ある商品の価格だけ別の番号に取り違えると、間違った金額で処理されます。

そこで、一つの商品に関係するデータと処理を一つのまとまりにするのがクラスです。クラスは「商品の設計図」、new で作った個々の商品は「インスタンス」と呼びます。

class Product {
    String name;
    int price;

    Product(String name, int price) {
        this.name = name;
        this.price = price;
    }
}

public class ProductDemo {
    public static void main(String[] args) {
        Product pen = new Product("ペン", 120);
        Product notebook = new Product("ノート", 250);
        System.out.println(pen.name + ": " + pen.price); // ペン: 120
        System.out.println(notebook.name + ": " + notebook.price); // ノート: 250
    }
}

Product pen は「Productを参照する変数penの宣言」、new Product(...) は「インスタンスの生成」です。this.name の this は生成されたインスタンス自身を指します。

同じクラスから作っても、penとnotebookの状態は別々です。

この節の理解度チェック(03-01)

問題: 商品名と価格を別々の配列で管理する場合、添字がずれるとどんな問題が起こりますか。クラスを使う利点も答えてください。

本文をいったん閉じ、自分の言葉で解答を書いてからヒント・解答を開いてください。

ヒント1:考える方向

別々の配列の同じ添字が対応することを前提としています。

ヒント2:注目する部分

添字がずれたら名前と価格がどう組み合わさるか考えます。

ヒント3:解法の手掛かり

Productならnameとpriceを同じインスタンスのフィールドとして保持できます。

模範解答と理由

例えばnames[0]の名前とprices[1]の価格を誤って組み合わせかねません。Productクラスなら名前と価格を一つのオブジェクトの状態としてまとめられます。

この節の自己採点基準(2点満点)

採点条件(各1点)

  1. 1点: 配列の添字のずれで別商品の名前と価格を誤対応させる危険を挙げる。
  2. 1点: Productクラスが名前と価格を一つのオブジェクトにまとめる利点を述べる。

正答例: 例えばnames[0]の名前とprices[1]の価格を誤って組み合わせかねません。Productクラスなら名前と価格を一つのオブジェクトの状態としてまとめられます。

部分点の例: 上記の独立した条件のうち満たした項目だけに1点ずつ加算し、満たさない項目は0点です。

誤答として扱う具体例: 名前と価格の配列は添字が違っても自動的に正しく結び付くと答える。

合格条件:2/2点。 個々の採点条件を満たした場合のみ加点します。

復習方法: 不足した採点条件の用語・具体例をこの節の本文で確認してから再回答してください。

次へ進む条件: 正解とその理由を説明できること。説明できない場合はこの節の例題へ戻ってください。

02. 初めてのクラスとインスタンス

次の例では Product(商品)クラスを作り、Main クラスから使用します。最初は、クラスが別のクラスから呼び出せることを確認しましょう。

// Product.java
public class Product {
    String name;
    int price;

    void printInfo() {
        System.out.println(name + ":" + price + "円");
    }
}
// Main.java
public class Main {
    public static void main(String[] args) {
        Product product = new Product();
        product.name = "ノート";
        product.price = 150;
        product.printInfo();
    }
}

実行結果:

ノート:150円

public class Product は商品を表すクラスの宣言です。String name と int price は商品ごとの情報を保持するフィールドです。

void printInfo() は表示を行うメソッドです。void は呼び出し元へ値を返さないことを表します。

Product product = new Product(); の左側は Product 型の変数 product を宣言しています。右側の new Product() で新しいインスタンスを作っています。

product.name のドット(.)は、そのインスタンスが持つフィールドやメソッドを指定するときに使います。

この段階では動きを理解しやすくするため、フィールドを直接変更しています。ただし、この書き方は後の節で改善します。実務では金額にマイナス値が入るといった不正な状態を防ぐ設計が大切です。

操作してみよう:別の Product インスタンスを作って名前と価格を設定し、2件の情報を表示してください。それぞれ別の値を保持できることを確認してください。


この節の理解度チェック(03-02)

問題: Product p = new Product();のProduct、p、new Product()はそれぞれ何ですか。

本文をいったん閉じ、自分の言葉で解答を書いてからヒント・解答を開いてください。

ヒント1:考える方向

左辺の型と変数名、右辺の式を分けます。

ヒント2:注目する部分

newの右側で何が生成されるか確認します。

ヒント3:解法の手掛かり

Productは型、pは参照変数、new Product()はインスタンス生成式です。

模範解答と理由

Productはクラス名(型)、pは参照を持つ変数、new Product()はインスタンスを生成する式です。

この節の自己採点基準(3点満点)

採点条件(各1点)

  1. 1点: Productをクラス名(型)と説明する。
  2. 1点: pをインスタンスへの参照を保持する変数と説明する。
  3. 1点: new Product()をインスタンス生成式と説明する。

正答例: Productはクラス名(型)、pは参照を持つ変数、new Product()はインスタンスを生成する式です。

部分点: 上記の正答要素が確認できた項目だけを加点します。3項目のうち一部だけ合っていれば、その項目数が得点です。言い換えは可。

誤答として扱う具体例: pそのものがクラス定義で、new Product()はメソッド呼び出し後に必ずnullを返すと答える。

合格条件:3/3点。 個々の採点条件を満たした場合のみ加点します。

復習: 0点だった各要素について、この節の説明と具体例を確認して再回答します。

次へ進む条件: 正解とその理由を説明できること。説明できない場合はこの節の例題へ戻ってください。

図解:クラスとインスタンス

クラスとインスタンス

図の読み方:クラスは設計図、newで生成される個々のオブジェクトがインスタンスです。

03. インスタンスごとの状態と参照

一つのクラスから複数のインスタンスを作ることができます。インスタンスごとに別々のフィールド値を持つことが、クラスを使う大きな利点です。

Product first = new Product();
first.name = "ペン";
Product second = new Product();
second.name = "消しゴム";
System.out.println(first.name);  // ペン
System.out.println(second.name); // 消しゴム

一方、Product another = first; と書いても、新しい商品は作られません。another と first は同じインスタンスを参照します。

このとき another.name を変更すると、first.name から見える値も変わります。これは初心者が混乱しやすい点です。

Product another = first;
another.name = "赤ペン";
System.out.println(first.name); // 赤ペン

null は参照先のインスタンスがないことを表す値です。

Product missing = null; の状態で missing.printInfo() を呼び出すと NullPointerException が発生します。

「変数がある」ことと「インスタンスが存在する」ことは区別してください。

確認問題:first == another はなぜ true になるのでしょうか。また、別々に new Product() した場合はどうなるでしょうか。


理解を深める:参照を代入したときに起こること

プリミティブ型の int を代入すると値がコピーされます。一方、クラス型の変数にはインスタンスそのものではなく、それを指し示す参照が入っています。参照を別の変数へ代入すると、二つの変数は同じインスタンスを指します。

Product a = new Product("消しゴム", 80);
Product b = a;
b.price = 90;
System.out.println(a.price); // 90
変数a ──┐
        ├──→ Productインスタンス [名前=消しゴム, 価格=90]
変数b ──┘

ここで b = a は、商品のコピーを作ったわけではありません。a と b のどちらから変更しても同じ商品の状態が変わります。

別のインスタンスが必要なら、明示的に new Product(a.name, a.price) などで作る必要があります。この例の図は理解用の模式図で、Javaの実際のメモリアドレスを表すものではありません。

理解を深める:クラスとインスタンスと参照

クラスは「商品というデータの形と操作」を定める設計図にたとえられます。new はその設計図に基づいて実体(インスタンス)を生成します。変数には参照が入るため、同じ実体を複数の変数から利用できることが重要です。

class Product {
    private String name;
    Product(String name) { this.name = name; }
    String getName() { return name; }
    void setName(String name) { this.name = name; }
}
public class ReferencePractice {
    public static void main(String[] args) {
        Product a = new Product("ノート");
        Product b = a;
        b.setName("ペン");
        System.out.println(a.getName()); // ペン
    }
}
変数 a ─┐
       ├─→ Productのインスタンス { name: "ペン" }
変数 b ─┘

Product b = a は新しい商品を生成していません。参照を別の変数へ代入しています。つまり b で商品名を更新すると、a から見える商品名も変わります。

Javaのメモリの正確な内部配置はJVMの実装に依存します。この図は参照関係を理解するための概念図です。

独学ステップ:二つの変数と一つのインスタンス

「変数が二つある」ことと「インスタンスが二つある」ことは同じではありません。

class Box {
    int value;
}

public class ReferencePractice {
    public static void main(String[] args) {
        Box first = new Box();
        first.value = 10;
        Box second = first;
        second.value = 20;
        System.out.println(first.value);
    }
}
first  ────────┐
               ▼
           ┌──────────┐
second ───>│ Box      │
           │ value=20 │
           └──────────┘

new Box() によってインスタンスが一つ作られます。Box second = first; は、そのインスタンスへの参照をもう一つの変数に渡します。インスタンスを新しく作ってはいません。

そのため second.value を変更すると、first.value から見える値も 20 になります。

レベルA: 実行する前に表示を予想し、実行結果と照合します。

レベルB: Box second = new Box(); に変更して、表示が変わる理由を説明します。

レベルC: Box third = second; を追加し、third.value = 30; のあとに何が表示されるか図で予想します。

レベルD: Box に値を取得するメソッドを追加し、参照が共有される状態を自分で検証します。

この節の理解度チェック(03-03)

問題: Box a=new Box(); Box b=a; a.value=20;のときb.valueはいくつですか(Boxのvalueの初期値は0とします)。

本文をいったん閉じ、自分の言葉で解答を書いてからヒント・解答を開いてください。

ヒント1:考える方向

bへ代入しているのは新しいBoxではありません。

ヒント2:注目する部分

aとbは同じオブジェクトを指すか、別々のオブジェクトか考えます。

ヒント3:解法の手掛かり

aを通じてvalueを20にすると、同じBoxを参照するbからも20が見えます。

模範解答と理由

20です。aとbは同じBoxを参照するため、a経由の変更がb経由でも見えます。

この節の自己採点基準(2点満点)

採点条件(各1点)

  1. 1点: b.valueが20と答える。
  2. 1点: aとbが同じBoxを参照しているためと説明する。

正答例: 20です。aとbは同じBoxを参照するため、a経由の変更がb経由でも見えます。

部分点の例: 20と答えたが参照の説明を誤った場合は1/2点。参照関係だけ正しく説明し値を誤った場合も1/2点。

誤答として扱う具体例: b.valueは初期値0のままだと答える。

合格条件:2/2点。 個々の採点条件を満たした場合のみ加点します。

復習方法: 不足した採点条件の用語・具体例をこの節の本文で確認してから再回答してください。

次へ進む条件: 正解とその理由を説明できること。説明できない場合はこの節の例題へ戻ってください。

図解:同じインスタンスの共有

同じインスタンスの共有

図の読み方:b = a とすると、aとbは同じインスタンスを参照します。

04. コンストラクタで初期値を設定する

先ほどの例では、商品を作った後で name と price を設定しました。しかし、設定し忘れると商品名は null、価格は 0 のままになります。

そこで、インスタンス生成時に必要な情報を受け取るコンストラクタを使います。

public class Product {
    String name;
    int price;

    public Product(String name, int price) {
        this.name = name;
        this.price = price;
    }

    void printInfo() {
        System.out.println(name + ":" + price + "円");
    }
}

呼び出し例は Product product = new Product("ノート", 150); です。

コンストラクタはクラス名と同じ名前を持ち、戻り値の型は書きません。

public Product(String name, int price) の丸括弧の中は、生成時に渡してほしい引数を受け取るための仮引数です。

this.name は現在のインスタンスのフィールドを意味し、右側の name はコンストラクタの仮引数です。同名でも this を使うことで区別できます。

引数の数や型が異なるコンストラクタを複数定義することをオーバーロードと呼びます。ただし、増やしすぎると使い方が複雑になるため、まずは一つのコンストラクタで必要な値を設定する書き方に慣れましょう。

注意:自分で引数付きコンストラクタを宣言すると、引数なしコンストラクタは自動では用意されません。そのため new Product() はコンパイルエラーになります。


理解を深める:コンストラクタ・this・カプセル化

public class Stock {
    private int quantity;
    public Stock(int quantity) {
        if (quantity < 0) {
            throw new IllegalArgumentException("初期在庫は0以上にしてください");
        }
        this.quantity = quantity;
    }
    public void add(int amount) {
        if (amount <= 0) {
            throw new IllegalArgumentException("追加数は1以上にしてください");
        }
        quantity += amount;
    }
    public int getQuantity() { return quantity; }
}

private にすると、別のクラスから直接 quantity を書き換えられなくなります。Stock(int quantity) はインスタンス生成時に呼ばれるコンストラクタで、初期値を検査します。

this.quantity はフィールド、右側の quantity は引数です。同じ名前のときに区別できます。add は「在庫を増やす」という許可された操作だけを公開し、不正な値は例外として拒否します。

演習: new Stock(-1) が失敗し、new Stock(3).add(2) の後の在庫が5になることを、main内で実行して確認します。

この節の理解度チェック(03-04)

問題: new Product("Pen",100)の"Pen"と100は、Productのコンストラクタで通常何に使われますか。

本文をいったん閉じ、自分の言葉で解答を書いてからヒント・解答を開いてください。

ヒント1:考える方向

コンストラクタが実行されるタイミングを確認します。

ヒント2:注目する部分

文字列と整数をそれぞれ商品情報へ割り当てます。

ヒント3:解法の手掛かり

"Pen"は商品名、100は価格の初期化に使うという設計例です。

模範解答と理由

新しいProductの名前と価格の初期値に使います。コンストラクタは生成時の初期化を担当します。

この節の自己採点基準(3点満点)

採点条件(各1点)

  1. 1点: "Pen"を商品名の初期値として利用すると述べる。
  2. 1点: 100を価格の初期値として利用すると述べる。
  3. 1点: コンストラクタによる生成時の初期化と結び付ける。

正答例: 新しいProductの名前と価格の初期値に使います。コンストラクタは生成時の初期化を担当します。

部分点の例: 上記の独立した条件のうち満たした項目だけに1点ずつ加算し、満たさない項目は0点です。

誤答として扱う具体例: "Pen"を型名、100を新しく生成するインスタンス数と答える。

合格条件:3/3点。 個々の採点条件を満たした場合のみ加点します。

復習方法: 不足した採点条件の用語・具体例をこの節の本文で確認してから再回答してください。

次へ進む条件: 正解とその理由を説明できること。説明できない場合はこの節の例題へ戻ってください。

図解:コンストラクタで初期化

コンストラクタで初期化

図の読み方:コンストラクタはnewによる生成時に実行され、オブジェクトを初期化します。

05. カプセル化とアクセス修飾子

今までの Product には問題があります。呼び出し元が product.price = -500; と書けてしまうためです。現実の商品にマイナス価格を設定したくない場合、フィールドの変更方法を制限します。

public class Product {
    private final String name;
    private int price;

    public Product(String name, int price) {
        if (name == null || name.isBlank()) {
            throw new IllegalArgumentException("商品名は必須です");
        }
        if (price < 0) {
            throw new IllegalArgumentException("価格は0以上にしてください");
        }
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public int getPrice() {
        return price;
    }

    public void changePrice(int newPrice) {
        if (newPrice < 0) {
            throw new IllegalArgumentException("価格は0以上にしてください");
        }
        this.price = newPrice;
    }
}

private は、そのクラスの外部からフィールドへ直接アクセスできないようにする修飾子です。public は外部から呼び出せることを示します。

getPrice() のように値を取得するメソッドをゲッターと呼びます。ここでは価格の変更方法を changePrice() に限定しており、変更前の値の検証ができます。

final の付いた name フィールドは、コンストラクタで一度設定した後に別の値を代入できません。

throw new IllegalArgumentException(...) は不正な引数が渡されたことを知らせるために例外を発生させています。例外については第04章でも詳しく学びます。

これらのように内部データと変更規則をクラス内にまとめ、外部へ必要な操作だけを公開する考え方がカプセル化です。「すべてのフィールドにゲッターとセッターを付ける」という意味ではありません。

むしろ、必要のない変更操作を公開しないことが重要です。

演習:Product の価格を100円に変更するコードを書いてください。また、-10 を設定しようとしたときに何が起きるか確かめてください。


理解を深める:privateにする理由を「壊れる例」から理解する

price を公開したままだと、pen.price = -100; と書けてしまいます。価格のルールを守らせるため、フィールドは private にし、作成時と変更時のどちらもチェックするのが基本です。

class SafeProduct {
    private int price;
    SafeProduct(int price) { setPrice(price); }
    public void setPrice(int price) {
        if (price < 0) throw new IllegalArgumentException("価格は0以上にしてください");
        this.price = price;
    }
    public int getPrice() { return price; }
}

private は「どこからも使えなくする」という意味ではなく、変更する入口を限定するために使います。すべてのフィールドにsetterが必要というわけではありません。

変更させるべきでなければgetterだけを用意する方法もあります。

この節の理解度チェック(03-05)

問題: 価格フィールドをpublicで直接変更できる場合、-100を代入される問題をどう防ぎますか。

本文をいったん閉じ、自分の言葉で解答を書いてからヒント・解答を開いてください。

ヒント1:考える方向

どこからでも価格を書き換えられる状態に注目します。

ヒント2:注目する部分

外部から直接フィールドへ代入できないようにします。

ヒント3:解法の手掛かり

privateにして、生成・変更の入口でprice>=0を検証します。

模範解答と理由

価格をprivateにし、コンストラクタや価格変更メソッドで非負値かを検査して、不正値を拒否します。単にsetterを作るだけでは防げません。

この節の自己採点基準(2点満点)

採点条件(各1点)

  1. 1点: 価格フィールドをprivateにして直接更新を制限すると説明する。
  2. 1点: コンストラクタまたは変更メソッドで負数を拒否すると説明する。

正答例: 価格をprivateにし、コンストラクタや価格変更メソッドで非負値かを検査して、不正値を拒否します。単にsetterを作るだけでは防げません。

部分点の例: 上記の独立した条件のうち満たした項目だけに1点ずつ加算し、満たさない項目は0点です。

誤答として扱う具体例: publicのままsetterを追加すれば直接の-100代入も防げると答える。

合格条件:2/2点。 個々の採点条件を満たした場合のみ加点します。

復習方法: 不足した採点条件の用語・具体例をこの節の本文で確認してから再回答してください。

次へ進む条件: 正解とその理由を説明できること。説明できない場合はこの節の例題へ戻ってください。

06. メソッドの役割と責務

メソッドには「そのクラスが担当する処理」を持たせます。例えば銀行口座の残高を変更する操作は、口座自身の deposit(入金)メソッドに任せると分かりやすくなります。

public class BankAccount {
    private int balance;

    public BankAccount(int initialBalance) {
        if (initialBalance < 0) {
            throw new IllegalArgumentException("初期残高は0以上です");
        }
        balance = initialBalance;
    }

    public void deposit(int amount) {
        if (amount <= 0) {
            throw new IllegalArgumentException("入金額は1以上です");
        }
        balance += amount;
    }

    public boolean withdraw(int amount) {
        if (amount <= 0) {
            throw new IllegalArgumentException("出金額は1以上です");
        }
        if (amount > balance) {
            return false;
        }
        balance -= amount;
        return true;
    }

    public int getBalance() {
        return balance;
    }
}

この例では、入金額が不正な場合に例外を発生させ、残高不足の場合は false を返しています。どちらも処理を失敗させる方法ですが、入力自体が不正なのか、正常な業務上の理由で実行できないのかを区別しています。

実務ではチームで方針を統一する必要があります。

なお、金融システムで小数を含む金額を計算するときに double を安易に使うのは危険です。この教材では整数の円単位に限定して扱います。

考えてみよう:Main が balance を直接変更する方法と、deposit を呼ぶ方法では、どちらが変更規則を守りやすいでしょうか。


この節の理解度チェック(03-06)

問題: 商品クラスのgetPrice()と注文クラスのcalculateTotal()は、どちらが複数商品から注文合計を計算する責務を持つのが自然ですか。

本文をいったん閉じ、自分の言葉で解答を書いてからヒント・解答を開いてください。

ヒント1:考える方向

商品1個の情報と、注文全体の計算を区別します。

ヒント2:注目する部分

数量や複数の商品の情報を持つのは通常どちらでしょうか。

ヒント3:解法の手掛かり

Productが価格を示し、OrderのcalculateTotalが注文合計を計算します。

模範解答と理由

通常は注文クラスのcalculateTotal()です。商品は自分の価格を提供し、注文が個数や複数商品の合計を扱います。

この節の自己採点基準(2点満点)

採点条件(各1点)

  1. 1点: OrderのcalculateTotalが注文合計の責務を持つと答える。
  2. 1点: Productは単価を提供し、Orderが数量等を扱うという理由を述べる。

正答例: 通常は注文クラスのcalculateTotal()です。商品は自分の価格を提供し、注文が個数や複数商品の合計を扱います。

部分点の例: 上記の独立した条件のうち満たした項目だけに1点ずつ加算し、満たさない項目は0点です。

誤答として扱う具体例: ProductのgetPriceが注文全体の複数商品の合計まで計算するべきだと答える。

合格条件:2/2点。 個々の採点条件を満たした場合のみ加点します。

復習方法: 不足した採点条件の用語・具体例をこの節の本文で確認してから再回答してください。

次へ進む条件: 正解とその理由を説明できること。説明できない場合はこの節の例題へ戻ってください。

07. static とインスタンスメンバー

これまで main メソッドは public static void main(String[] args) と書いてきました。

static は、個々のインスタンスではなくクラスに属するメンバーであることを表します。

public class TaxCalculator {
    public static int addTax(int price) {
        return price * 110 / 100;
    }
}

呼び出し側は TaxCalculator.addTax(1000) と書けます。new TaxCalculator() は必要ありません。

一方、銀行口座の残高は口座ごとに異なるので、static なフィールドにするとすべての口座で値を共有することになり、通常は望ましくありません。

static にすれば何でも便利になるわけではありません。インスタンスの状態を必要としない計算処理などに使う、と理解しましょう。なお、上の税計算は学習用の単純化した整数計算です。

実際の消費税計算では税率や端数処理の仕様を確認する必要があります。


この節の理解度チェック(03-07)

問題: staticフィールドの値を同じクラスの2つのインスタンスから見た場合、別々の値を持ちますか。

本文をいったん閉じ、自分の言葉で解答を書いてからヒント・解答を開いてください。

ヒント1:考える方向

staticの有無でデータの所有先が違います。

ヒント2:注目する部分

インスタンスごとに作られるフィールドとクラスのフィールドを比較します。

ヒント3:解法の手掛かり

staticはクラスに属して共有され、通常のインスタンスフィールドは個々に保持されます。

模範解答と理由

通常は別々に持たず、そのクラスの共有フィールドを参照します。インスタンスフィールドはインスタンスごとに独立します。

この節の自己採点基準(2点満点)

採点条件(各1点)

  1. 1点: staticフィールドはクラスで共有すると答える。
  2. 1点: インスタンスフィールドはインスタンスごとに独立すると区別する。

正答例: 通常は別々に持たず、そのクラスの共有フィールドを参照します。インスタンスフィールドはインスタンスごとに独立します。

部分点の例: 上記の独立した条件のうち満たした項目だけに1点ずつ加算し、満たさない項目は0点です。

誤答として扱う具体例: staticフィールドはnewした回数だけ別々に確保されると答える。

合格条件:2/2点。 個々の採点条件を満たした場合のみ加点します。

復習方法: 不足した採点条件の用語・具体例をこの節の本文で確認してから再回答してください。

次へ進む条件: 正解とその理由を説明できること。説明できない場合はこの節の例題へ戻ってください。

08. クラスの継承とオーバーライド

似た特徴を持つクラスがあるとき、共通の特徴を親クラスにまとめ、子クラスへ受け継がせることができます。これを継承と呼びます。Javaでは extends を使います。

Employee.java に次のクラスを保存します。

public class Employee {
    private final String name;

    public Employee(String name) {
        this.name = name;
    }

    public String getDescription() {
        return "従業員:" + name;
    }
}

Engineer.java に次のクラスを保存します。Javaでは、公開クラスをそれぞれ同名のファイルに分けます。

public class Engineer extends Employee {
    public Engineer(String name) {
        super(name);
    }

    @Override
    public String getDescription() {
        return "エンジニア:" + super.getDescription();
    }
}

super(name) は親クラスのコンストラクタを呼び出します。@Override は親クラスのメソッドをオーバーライド(子クラスで振る舞いを再定義)していることを表すアノテーションです。

タイプミスなどで実際には上書きできていない場合、コンパイラが検出する手助けになります。

オーバーロードは同じ名前のメソッドを引数の違いで複数定義すること、オーバーライドは親クラスなどから受け継いだメソッドを再定義することです。名前が似ていますが違う仕組みなので区別してください。

継承は「共通のコードがあるから」という理由だけで採用するものではありません。「エンジニアは従業員の一種である」といった関係が自然な場合に検討します。

共通処理の再利用だけが目的なら、別のクラスに委譲するほうが変更に強い場合があります。


この節の理解度チェック(03-08)

問題: Employee e = new Manager();でManagerがEmployeeのwork()をオーバーライドしているとき、e.work()は通常どちらの実装を呼びますか。

本文をいったん閉じ、自分の言葉で解答を書いてからヒント・解答を開いてください。

ヒント1:考える方向

eの宣言型と、newした実体の型を分けて考えます。

ヒント2:注目する部分

workはオーバーライドされたインスタンスメソッドです。

ヒント3:解法の手掛かり

実体がManagerならe.work()はManagerのworkを呼びます。

模範解答と理由

Managerのwork()を呼びます。オーバーライドされたインスタンスメソッドは実際のオブジェクトの型で実装が決まります。

この節の自己採点基準(2点満点)

採点条件(各1点)

  1. 1点: Managerのwork()が呼ばれると答える。
  2. 1点: オーバーライドされたインスタンスメソッドは実行時の型で選ばれると説明する。

正答例: Managerのwork()を呼びます。オーバーライドされたインスタンスメソッドは実際のオブジェクトの型で実装が決まります。

部分点の例: 上記の独立した条件のうち満たした項目だけに1点ずつ加算し、満たさない項目は0点です。

誤答として扱う具体例: 参照変数eの宣言がEmployeeなので常にEmployee.work()を呼ぶと答える。

合格条件:2/2点。 個々の採点条件を満たした場合のみ加点します。

復習方法: 不足した採点条件の用語・具体例をこの節の本文で確認してから再回答してください。

次へ進む条件: 正解とその理由を説明できること。説明できない場合はこの節の例題へ戻ってください。

図解:継承とオーバーライド

継承とオーバーライド

図の読み方:子クラスは親クラスの機能を引き継ぎ、必要ならメソッドを上書きできます。

09. ポリモーフィズムと動的ディスパッチ

**ポリモーフィズム(多態性)**とは、共通の型で扱いながら、実際のインスタンスに応じた処理が実行される性質です。

Employee employee = new Engineer("田中");
System.out.println(employee.getDescription());

変数 employee の型は Employee ですが、参照している実体は Engineer です。getDescription() を呼ぶと Engineer 側でオーバーライドしたメソッドが実行されます。

これを動的ディスパッチと呼びます。

この仕組みがあると、従業員の種類が増えても、呼び出し側で if を何度も増やさずに共通の操作を呼べます。ただし、親型に存在しない子クラス固有のメソッドは、そのままでは呼べません。

まずは「変数の型」と「実際のインスタンスの型」が異なる場合があることを理解してください。


理解を深める:ポリモーフィズムが役に立つとき

メール通知・画面通知などの具体的な処理が違っても、呼び出し側からは notify(String message) という同じ操作で扱えると便利です。インターフェースはその約束事を表します。

Notification n = new EmailNotification(); としても、実行時に呼ばれるのは具体的な EmailNotification の実装です。これが動的ディスパッチの一例です。

この節の理解度チェック(03-09)

問題: Animal a = new Dog(); a.speak();でDogがspeakをオーバーライドしている場合、なぜDogの処理が選ばれますか。

本文をいったん閉じ、自分の言葉で解答を書いてからヒント・解答を開いてください。

ヒント1:考える方向

Animalは参照型ですが、newしているのはDogです。

ヒント2:注目する部分

speakはオーバーライドされたインスタンスメソッドです。

ヒント3:解法の手掛かり

実体Dogに基づく動的ディスパッチでDog.speakが選ばれます。

模範解答と理由

実体がDogであり、オーバーライドされたインスタンスメソッドは実行時のオブジェクト型に応じて呼び分けられるためです。

この節の自己採点基準(2点満点)

採点条件(各1点)

  1. 1点: Dogのspeak()が実行されると答える。
  2. 1点: 実行時のオブジェクト型に応じた動的ディスパッチが理由と説明する。

正答例: 実体がDogであり、オーバーライドされたインスタンスメソッドは実行時のオブジェクト型に応じて呼び分けられるためです。

部分点の例: この節は正答要素が1つなので部分点はありません。

誤答として扱う具体例: 参照型がAnimalならAnimal.speakしか呼べないと答える。

合格条件:2/2点。 個々の採点条件を満たした場合のみ加点します。

復習方法: 不足した採点条件の用語・具体例をこの節の本文で確認してから再回答してください。

次へ進む条件: 正解とその理由を説明できること。説明できない場合はこの節の例題へ戻ってください。

10. 抽象クラスとインターフェース

共通の操作だけを決め、具体的な処理を実装側に任せたい場合があります。インターフェースは、クラスが外部に提供する操作の約束を表す仕組みです。

public interface NotificationSender {
    void send(String message);
}

public class ConsoleNotificationSender implements NotificationSender {
    @Override
    public void send(String message) {
        System.out.println("通知:" + message);
    }
}

implements はインターフェースを実装することを表します。NotificationSender 型で受け取る側は、実際の送信方法が画面表示か別の方法かを知らなくても send を呼び出せます。

これはプログラムの部品を交換しやすくするための重要な考え方です。

抽象クラスは abstract class と書き、共通する実装と未実装の抽象メソッドを併せ持てます。

例えば、abstract class Report に共通の出力処理を持たせ、個別の createBody() のみ子クラスに実装させる構成です。

Javaのクラス継承は一つの直接の親クラスに限られますが、複数のインターフェースを実装することはできます。

初学者の段階では、違いを丸暗記するよりも「外部に何を約束するか」がインターフェース、「関連するクラス間で共通の状態や処理を持つ」場合に抽象クラスを検討する、と理解すれば十分です。


理解を深める:継承・インターフェース・コンポジション

継承は「AはBの一種」の関係が成り立つときに使うことがあります。インターフェースは異なるクラスに共通の操作を約束します。コンポジションは、あるクラスが別のオブジェクトを部品として持つ構成です。

interface DiscountRule {
    int discount(int originalPrice);
}
class NoDiscount implements DiscountRule {
    public int discount(int originalPrice) { return originalPrice; }
}
class TenPercentOff implements DiscountRule {
    public int discount(int originalPrice) { return originalPrice * 90 / 100; }
}
class Checkout {
    private final DiscountRule rule;
    Checkout(DiscountRule rule) { this.rule = rule; }
    int total(int price) { return rule.discount(price); }
}

Checkout は DiscountRule を「持つ」ため、割引方法を差し替えられます。Checkout を継承して割引種類ごとに子クラスを増やす必要はありません。

new Checkout(new TenPercentOff()).total(1000) は900です。

発展演習: 固定50円割引の FiftyYenOff を追加します。ただし負の合計にならないようにしてください。期待値は価格100円で50円、価格30円で0円です。

設計の評価は「新しいクラスの追加だけでCheckoutのコード変更が不要か」です。

独学ステップ:継承より部品として組み合わせるべき場面

たとえば「注文には商品が含まれる」という関係は、注文は商品の一種であるという意味ではありません。この場合に class Order extends Product とすると、実際の業務上の関係とコードが合わなくなります。

誤解しやすい設計: Order ──extends──> Product (注文は商品である?)

自然な設計:       Order ──has a──> Product (注文は商品を持つ)

Javaでは次のようにフィールドとして保持できます。

class Product {
    String name;
    Product(String name) { this.name = name; }
}

class Order {
    private final Product product;
    Order(Product product) { this.product = product; }
    String describe() { return product.name + "の注文"; }
}

これがコンポジションを使った設計の小さな例です。継承を選ぶ前に「AはBの一種か」、部品として持つ前に「AはBを利用・保持するか」と問いかけます。

演習: 「社員と部署」「車とエンジン」「犬と動物」の三組について、どの関係が自然か考え、その理由を文章で書いてください。答えを覚えるのではなく、業務上の意味とコードの関係が一致しているかを確かめます。

この節の理解度チェック(03-10)

問題: NotificationSenderインターフェースをEmailSenderとSmsSenderが実装する利点を一つ答えてください。

本文をいったん閉じ、自分の言葉で解答を書いてからヒント・解答を開いてください。

ヒント1:考える方向

メール送信とSMS送信に共通の呼び出し口を考えます。

ヒント2:注目する部分

呼び出し側が具体的なEmailSenderだけに依存するとどうなるでしょうか。

ヒント3:解法の手掛かり

NotificationSenderに依存すると実装の差し替えが容易になります。

模範解答と理由

呼び出し側をNotificationSenderに依存させれば、メールとSMSの送信処理を差し替えやすくなります。

この節の自己採点基準(2点満点)

採点条件(各1点)

  1. 1点: 通知インターフェースを使うと送信方式を差し替えやすいと説明する。
  2. 1点: 呼び出し側が具体クラスへの依存を減らせると説明する。

正答例: 呼び出し側をNotificationSenderに依存させれば、メールとSMSの送信処理を差し替えやすくなります。

部分点の例: この節は正答要素が1つなので部分点はありません。

誤答として扱う具体例: インターフェースがあるとEmailSenderとSmsSenderを同じコードに実装しなければならないと答える。

合格条件:2/2点。 個々の採点条件を満たした場合のみ加点します。

復習方法: 不足した採点条件の用語・具体例をこの節の本文で確認してから再回答してください。

次へ進む条件: 正解とその理由を説明できること。説明できない場合はこの節の例題へ戻ってください。

11. コンポジション:別のクラスを部品として持つ

継承以外にも、あるクラスが別のクラスをフィールドとして持つコンポジション(組み合わせ)があります。たとえば注文は商品を含みますが、「注文は商品の一種」ではありません。この場合、注文が商品を持つ構成が自然です。

public class Order {
    private final Product product;
    private final int quantity;

    public Order(Product product, int quantity) {
        if (product == null || quantity <= 0) {
            throw new IllegalArgumentException("商品と数量を確認してください");
        }
        this.product = product;
        this.quantity = quantity;
    }

    public int getTotalPrice() {
        return product.getPrice() * quantity;
    }
}

この書き方では、注文に関する計算を Order に閉じ込められます。実務では大きなクラス一つにすべてを押し込むより、役割に沿って複数クラスに分けることが重要です。

なお、金額の桁あふれや注文後の価格変更をどう扱うかは業務仕様によって異なります。この例では学習を優先して省略しています。


理解を深める:継承とコンポジションはどのように使い分けるのか

継承は「AはBの一種である」と自然に説明できる関係(例:特定の種類の通知)で使う候補になります。しかし「注文は商品を持っている」は、注文が商品の一種という意味ではありません。

したがって注文クラスの中に商品クラスをフィールドとして持たせるコンポジションが自然です。

class OrderLine {
    private final SafeProduct product;
    private final int quantity;
    OrderLine(SafeProduct product, int quantity) {
        if (quantity <= 0) throw new IllegalArgumentException("数量は1以上");
        this.product = product;
        this.quantity = quantity;
    }
    int total() { return product.getPrice() * quantity; }
}

extends SafeProduct とするのは不適切です。注文行は商品の一種ではないからです。「〜である」か「〜を持つ/利用する」か、まず日本語で言い換えて判断してみてください。

この節の理解度チェック(03-11)

問題: OrderがProductを部品として保持することと、OrderがProductを継承することではどちらが自然ですか。

本文をいったん閉じ、自分の言葉で解答を書いてからヒント・解答を開いてください。

ヒント1:考える方向

注文は商品そのものと言えるか考えます。

ヒント2:注目する部分

is-a関係とhas-a関係を区別します。

ヒント3:解法の手掛かり

OrderはProductではなくProductを含むので継承よりコンポジションです。

模範解答と理由

OrderがProductを保持するコンポジション(has-a)が自然です。注文は商品ではないため、Productの継承(is-a)は適切ではありません。

この節の自己採点基準(2点満点)

採点条件(各1点)

  1. 1点: OrderがProductを保持するコンポジションを選ぶ。
  2. 1点: 注文は商品ではないため継承のis-a関係にならないと説明する。

正答例: OrderがProductを保持するコンポジション(has-a)が自然です。注文は商品ではないため、Productの継承(is-a)は適切ではありません。

部分点の例: 上記の独立した条件のうち満たした項目だけに1点ずつ加算し、満たさない項目は0点です。

誤答として扱う具体例: OrderはProductの一種なのでextends Productにすべきと答える。

合格条件:2/2点。 個々の採点条件を満たした場合のみ加点します。

復習方法: 不足した採点条件の用語・具体例をこの節の本文で確認してから再回答してください。

次へ進む条件: 正解とその理由を説明できること。説明できない場合はこの節の例題へ戻ってください。

図解:コンポジション

コンポジション

図の読み方:別のクラスを部品として持つのがコンポジションです(has-a関係)。

12. パッケージとファイルの分け方

Javaでは、クラスを整理するためにパッケージを使います。パッケージは名前のまとまりで、たとえば com.example.training.model のようにドットで区切って表します。

package com.example.training.model;

public class Product {
    // クラスの内容
}

別のパッケージからこのクラスを使う場合は、原則として import com.example.training.model.Product; を書くか、完全修飾名で指定します。

Eclipseでは「New > Package」でパッケージを作成でき、Javaクラスの作成時に指定できます。

Javaの基本的なルールとして、公開クラス public class Product は通常 Product.java という名前のファイルに配置します。パッケージ名とディレクトリ構成も、通常は対応させます。

Mavenの一般的な構成ではJavaコードを src/main/java 以下に配置します。

アクセス修飾子を指定しない場合は、同じパッケージ内から利用できるパッケージプライベートとなります。protected は同一パッケージおよび条件を満たすサブクラスから利用できる修飾子です。

最初は public と private を使い分ける練習を優先しましょう。


この節の理解度チェック(03-12)

問題: package training.shop;と宣言したProduct.javaを標準的なMavenプロジェクトに置くフォルダーはどこですか。

本文をいったん閉じ、自分の言葉で解答を書いてからヒント・解答を開いてください。

ヒント1:考える方向

Javaのパッケージ名のドットをフォルダー区切りに読み替えます。

ヒント2:注目する部分

Mavenの製品コードのルートsrc/main/javaから考えます。

ヒント3:解法の手掛かり

training.shopはtraining/shopに対応し、Product.javaをその配下に置きます。

模範解答と理由

通常のMavenプロジェクトではsrc/main/java/training/shop/Product.javaに置きます。Javaのパッケージ名training.shopとフォルダー階層training/shopを対応させるのが標準的な配置です。

この節の自己採点基準(2点満点)

採点条件(各1点)

  1. 1点: src/main/java/training/shop/Product.javaと答える。
  2. 1点: training.shopがtraining/shopの階層に対応すると説明する。

正答例: 通常のMavenプロジェクトではsrc/main/java/training/shop/Product.javaに置きます。Javaのパッケージ名training.shopとフォルダー階層training/shopを対応させるのが標準的な配置です。

部分点の例: 上記の独立した条件のうち満たした項目だけに1点ずつ加算し、満たさない項目は0点です。

誤答として扱う具体例: src/main/java/training.shop/Product.javaが標準的配置と答える。

合格条件:2/2点。 個々の採点条件を満たした場合のみ加点します。

復習方法: 不足した採点条件の用語・具体例をこの節の本文で確認してから再回答してください。

次へ進む条件: 正解とその理由を説明できること。説明できない場合はこの節の例題へ戻ってください。

13. Javaでよくある間違いと対処

「フィールドが private であるためアクセスできません」

呼び出し元で product.price = 100; と書いたのに、price を private に変更した場合などに発生します。

直接書き換えるのではなく、公開された changePrice などのメソッドを使ってください。エラーを消すためだけに private を外すのは、設計意図を壊す可能性があります。

「コンストラクタが定義されていません」

new Product() と書いたのに Product(String, int) しか用意されていない場合、必要な引数を渡してください。引数の順番と型にも注意しましょう。

NullPointerException

参照変数の値が null のままメソッドを呼んだ可能性があります。どこでインスタンスを作ったか、どこで null が設定されたか、順番に確認してください。

@Override のエラー

メソッド名、引数の数と型、戻り値の互換性を確認してください。getDescription() と getDescriptions() のような綴りの違いもよくある原因です。

実行結果が予想と違う

「どのインスタンスのフィールドを変更したか」を確認してください。参照変数を別名に代入しても、新しいインスタンスは作成されません。Eclipseのデバッガーでブレークポイントを設定し、一行ずつ値を確認する方法も有効です。


この節の理解度チェック(03-13)

問題: NullPointerExceptionが起こり得るコードProduct p=null; p.getPrice();をどう直せますか。

本文をいったん閉じ、自分の言葉で解答を書いてからヒント・解答を開いてください。

ヒント1:考える方向

pは現時点でnullです。

ヒント2:注目する部分

nullの参照にはgetPriceを呼び出せません。

ヒント3:解法の手掛かり

実体を代入してから呼ぶか、nullである場合を分岐します。

模範解答と理由

必要なProductを生成・取得してから呼び出します。値がないことが正常なケースなら適切な分岐や設計を行います。nullのままメソッドは呼べません。

この節の自己採点基準(2点満点)

採点条件(各1点)

  1. 1点: 必要なProductのインスタンスを取得・生成してから呼ぶと説明する。
  2. 1点: nullがあり得る設計ならチェックや分岐を行うと説明する。

正答例: 必要なProductを生成・取得してから呼び出します。値がないことが正常なケースなら適切な分岐や設計を行います。nullのままメソッドは呼べません。

部分点の例: 上記の独立した条件のうち満たした項目だけに1点ずつ加算し、満たさない項目は0点です。

誤答として扱う具体例: nullの変数からgetPriceを呼べばJavaがProductを自動生成すると答える。

合格条件:2/2点。 個々の採点条件を満たした場合のみ加点します。

復習方法: 不足した採点条件の用語・具体例をこの節の本文で確認してから再回答してください。

次へ進む条件: 正解とその理由を説明できること。説明できない場合はこの節の例題へ戻ってください。

14. 総合演習:商品と注文をモデル化する

最後に、小規模な注文管理の仕組みを考えます。商品 Product は商品名と価格を持ち、注文 Order は商品と数量を持ちます。Main は商品と注文を作成し、合計金額を表示します。

まず、次の要件を読み、設計を考えてください。

  1. 商品名は空文字にできない。
  2. 商品価格は0円以上とする。
  3. 注文数量は1個以上とする。
  4. 商品の情報は外部から直接変更できない。
  5. 注文の合計金額を取得できる。

要件の文章を読んだだけでコードを書き始めるのではなく、「どのクラスがどの情報を持つか」「不正な入力はどこで拒否するか」を先に整理してください。完成例は examples/03_oop/06_order に収録しています。

受講者用課題と講師用解答は別ファイルです。


理解を深める:実務の調査ポイント

既存コードに private なフィールドがある場合、直接変更できない理由を確認し、公開メソッドの契約を先に読みます。

継承を検討する際は本当に「〜の一種」なのか、振る舞いを差し替えるだけならインターフェース+コンポジションの方が適切ではないかを考えます。完成後は正常値・境界値・不正値の3種類を必ず試します。

この節の理解度チェック(03-14)

問題: 商品と注文を新しく設計する場合、ProductとOrderそれぞれに持たせる状態を具体的に一つずつ挙げてください。

本文をいったん閉じ、自分の言葉で解答を書いてからヒント・解答を開いてください。

ヒント1:考える方向

商品自体の情報と注文の情報を分けます。

ヒント2:注目する部分

商品の名前・価格と、注文の明細・数量がそれぞれ候補です。

ヒント3:解法の手掛かり

Productはnameやprice、Orderはitemsやquantityを保持する例を挙げます。

模範解答と理由

例:Productはnameとprice、Orderはitemsとquantityなどを保持します。Orderが商品の価格に注文数量を掛けて金額を計算する構成が考えられます。

この節の自己採点基準(2点満点)

採点条件(各1点)

  1. 1点: Productの状態としてnameまたはpriceを具体的に挙げる。
  2. 1点: Orderの状態としてitemsまたはquantityなどを具体的に挙げる。

正答例: 例:Productはnameとprice、Orderはitemsとquantityなどを保持します。Orderが商品の価格に注文数量を掛けて金額を計算する構成が考えられます。

部分点の例: 上記の独立した条件のうち満たした項目だけに1点ずつ加算し、満たさない項目は0点です。

誤答として扱う具体例: ProductとOrderはどちらも必ずクラス名だけ持てばよいと答える。

合格条件:2/2点。 個々の採点条件を満たした場合のみ加点します。

復習方法: 不足した採点条件の用語・具体例をこの節の本文で確認してから再回答してください。

次へ進む条件: 正解とその理由を説明できること。説明できない場合はこの節の例題へ戻ってください。

15. 理解度チェック

次の問いに、自分の言葉で答えてください。必要ならコードを書いて実行して構いません。

  1. クラスとインスタンスの違いは何ですか。
  2. new を使うと何が起こりますか。
  3. Product a = new Product(...); Product b = a; としたとき、オブジェクトは何個ありますか。
  4. コンストラクタはいつ実行されますか。
  5. this.name = name; の左右の name は何が違いますか。
  6. private を使うのはなぜですか。
  7. static フィールドに口座残高を保存すると、どのような問題がありますか。
  8. オーバーロードとオーバーライドはどう違いますか。
  9. Employee e = new Engineer("山田"); の e からオーバーライドされたメソッドを呼ぶとどうなりますか。
  10. 「注文が商品を持つ」という関係を表すとき、継承とコンポジションのどちらが自然ですか。理由も説明してください。

すぐに答えられない問いがある場合は該当節に戻り、コードを実行して確認してください。説明を読んで理解したつもりになるだけでなく、実際に確かめることが大切です。


理解を深める:理解を確認する

  1. Product b = a; と new Product(...) では、インスタンスの数がどう違いますか。
  2. 負の価格を防ぐには、フィールドの公開とsetterのどちらが適していますか。その理由も説明してください。
  3. 「注文」と「商品」は継承とコンポジションのどちらですか。日本語で説明してください。

他言語経験者向け補足:Javaでは参照の代入はオブジェクトの複製ではありません。== は参照の同一性、equals はクラスが定義する等価性の判定に使います。

equals の振る舞いはクラスによるため、任意のクラスで内容比較になるとは限りません。

この節の理解度チェック(03-15)

問題: 同じクラスから作った2つのインスタンスで、インスタンスフィールドの値が異なり得る理由を答えてください。

本文をいったん閉じ、自分の言葉で解答を書いてからヒント・解答を開いてください。

ヒント1:考える方向

クラス定義は同じでもnewの結果は同一とは限りません。

ヒント2:注目する部分

newを2回実行すると何個のインスタンスができますか。

ヒント3:解法の手掛かり

それぞれ別のオブジェクトにインスタンスフィールドを保持できるため値は異なります。

模範解答と理由

newを2回行えば通常は別々のインスタンスになり、それぞれのフィールドに異なる値を保持できます。

この節の自己採点基準(2点満点)

採点条件(各1点)

  1. 1点: newを別々に行うと別インスタンスを作ると答える。
  2. 1点: インスタンスごとにフィールドの値を保持できると説明する。

正答例: newを2回行えば通常は別々のインスタンスになり、それぞれのフィールドに異なる値を保持できます。

部分点の例: この節は正答要素が1つなので部分点はありません。

誤答として扱う具体例: 同じクラスから生成された全オブジェクトのインスタンスフィールドは共有されると答える。

合格条件:2/2点。 個々の採点条件を満たした場合のみ加点します。

復習方法: 不足した採点条件の用語・具体例をこの節の本文で確認してから再回答してください。

次へ進む条件: 正解とその理由を説明できること。説明できない場合はこの節の例題へ戻ってください。

16. この章のまとめと次章への準備

この章で学んだ考え方は、Javaの実務開発の土台です。大規模なWebシステムでも、データを保持するクラス、業務処理を担当するクラス、外部との入出力を扱うクラスなどが連携しています。

クラスを作るときには「何を担当するクラスか」「外に公開する操作は何か」「不正な状態を作れないか」を確認しましょう。継承やインターフェースは、使うこと自体が目的ではありません。

変更に対応しやすく、読みやすい設計にするための選択肢です。

次の第04章では、例外処理、コレクション、ジェネリクス、Stream API、ファイル入出力などを扱います。複数の商品を一覧で管理する、失敗した処理を安全に扱うといった実務に近い処理へ進んでいきます。

学習内容の確認ポイント

継承は「一種である」(is-a)関係に限定して考え、部品を持つ関係はコンポジションも検討してください。クラスと参照を学ぶ際は2つの変数が同じインスタンスを指す場合と、別のインスタンスを指す場合を図に描いてから実行結果を比較します。

学習内容を振り返るための操作確認

この節は、本文の例題や演習が実行できないときに戻るための案内です。最初にファイルの場所と作業中のツールを確認し、コマンドを貼り付ける前に現在のフォルダーを確認してください。

実行する場所と順序

examples/03_oop 配下の節ごとのフォルダーで、同じフォルダー内のJavaファイルをまとめてコンパイルして実行します。違う節の Product.java を混ぜないでください。

成功・失敗の判断

Javaの出力だけでなく、インスタンスを2つ作った場合の値の違いを説明します。

途中で止まったとき

duplicate class が出た場合は異なる節の同名クラスを同時にコンパイルしていないか確認します。

作業記録の書き方

  1. 利用環境(OS、Java、必要なツールのバージョン)を記録します。
  2. 実際に操作したフォルダー、実行コマンド、入力内容を記録します。
  3. 期待した結果と実際の結果を別々に書きます。失敗時にはエラーメッセージの全文を保管します。
  4. 修正を加えた場合は変更箇所を記録し、同じ手順で再確認します。
  5. 成功を確認できていなければ「未確認」と記載し、成功と推測して次へ進まないでください。

実際の入力、実行結果、原因の説明、演習の提出物がそろって初めて章の学習を完了とします。

この節の理解度チェック(03-16)

問題: 次章のListへ進む前に、1個のProductと複数個のProductを扱う場合の違いを説明してください。

本文をいったん閉じ、自分の言葉で解答を書いてからヒント・解答を開いてください。

ヒント1:考える方向

1件の参照と複数件の入れ物を区別します。

ヒント2:注目する部分

Product pにはProductを1件参照できます。

ヒント3:解法の手掛かり

複数のProductを可変長で扱うならListなどを使います。

模範解答と理由

1個ならProduct pのような参照を使い、複数件を可変長で扱うならList<Product>などのコレクションを使うと便利です。

この節の自己採点基準(2点満点)

採点条件(各1点)

  1. 1点: 1件ならProduct型の変数を用いる例を示す。
  2. 1点: 複数件ならListなどを用いる例を示す。

正答例: 1個ならProduct pのような参照を使い、複数件を可変長で扱うならList<Product>などのコレクションを使うと便利です。

部分点: 上記の正答要素が確認できた項目だけを加点します。2項目のうち一部だけ合っていれば、その項目数が得点です。言い換えは可。

誤答として扱う具体例: Product pだけで多数の商品を自動的にリスト管理できると答える。

合格条件:2/2点。 個々の採点条件を満たした場合のみ加点します。

復習: 0点だった各要素について、この節の説明と具体例を確認して再回答します。

次へ進む条件: 正解とその理由を説明できること。説明できない場合はこの節の例題へ戻ってください。