Java / LESSON 08 / JV08

Java:クラスとオブジェクト

1学習の目的

2基礎解説

JV06・JV07では名前・価格・在庫を3つの配列で管理していました。しかしこの方法は、配列の長さがずれると破綻します。「ノートPCの価格は128000円」という関係が、コード上のどこにも書かれていないからです。

用語意味
クラス設計図class Product { … }
オブジェクト実際のモノnew Product(…)
フィールド持つデータString name;
コンストラクタ作るときの処理Product(…) { … }
✅ 覚えるべき重要ポイント
  1. クラスは設計図、オブジェクトは実物class Product が設計図で、new Product("ノートPC", 128000, 5) が実際の商品1つ。1つの設計図から何個でも作れる
  2. コンストラクタはクラス名と同じ名前で、戻り値の型を書かない。new したときに自動で呼ばれ、初期値を設定する。
  3. this は「このオブジェクト自身」。引数と同じ名前のフィールドを区別するために this.name = name; と書く。
  4. データと処理を同じ場所に置ける。在庫金額の計算は、商品自身が知っているべき情報——product.stockValue() と書けるのが自然。
  5. toString() を定義すると出力の形を決められる。定義しないと Product@1b6d3586 のような意味不明な表示になる。

現場使用例:商品・顧客・注文・社員など、業務で扱うあらゆるデータ。実務のJavaプログラムは、ほぼすべてクラスの組み合わせでできている。

public class Main {

    // ① クラス(設計図)を定義する
    static class Product {
        // フィールド(持つデータ)
        String name;
        int price;
        int stock;

        // コンストラクタ(作るときの処理)
        Product(String name, int price, int stock) {
            this.name = name;      // this は「自分自身」
            this.price = price;
            this.stock = stock;
        }

        // メソッド(できること)
        int stockValue() {
            return price * stock;   // 自分のフィールドが使える
        }

        String label() {
            if (stock == 0) return "品切れ";
            if (stock < 10) return "残りわずか";
            return "在庫あり";
        }

        // 出力の形を決める
        @Override
        public String toString() {
            return name + "(" + price + "円)";
        }
    }

    public static void main(String[] args) {
        // ② オブジェクトを作る
        Product p = new Product("ノートPC", 128000, 5);

        // ③ 使う
        System.out.println(p.name);          // ノートPC
        System.out.println(p.stockValue());  // 640000
        System.out.println(p.label());       // 残りわずか
        System.out.println(p);               // toString が使われる

        // オブジェクトの配列(名前・価格・在庫が常にセット)
        Product[] items = {
            new Product("ノートPC", 128000, 5),
            new Product("マウス", 3200, 42),
        };

        for (Product item : items) {
            System.out.println(item.name + ":" + item.stockValue());
        }
    }
}

3基本ドリル(10問)

JV08-D01 Product クラスを定義せよ(フィールド:name・price)。コンストラクタで値を受け取り、1つ作って name を出力すること。 コーディング★☆☆無料
期待される結果

出力 == ノートPC

ヒント

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

模範解答
public class Main {

    static class Product {
        String name;
        int price;

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

    public static void main(String[] args) {
        Product p = new Product("ノートPC", 128000);

        System.out.println(p.name);
    }
}
解説

クラスが設計図、new で実物を作る。この Product という型は、Javaが最初から持っている int や String と同じように使える。

JV08-D02 コンストラクタの特徴として正しいものはどれか。
A. 戻り値の型を書く B. クラス名と同じ名前で戻り値の型を書かない C. main と同じ名前 D. void を書く
選択★☆☆無料
期待される結果

解答 == B

ヒント

new したときに呼ばれる特別なメソッド。

模範解答
B
解説

コンストラクタは戻り値の型を書かない。もし void Product(...) と書くと、ただのメソッドとして扱われてしまう。

JV08-D03 Product クラスにメソッド stockValue() を追加せよ(price × stock を返す)。ノートPC/128000/5 で試して出力すること。 コーディング★☆☆無料
期待される結果

出力 == 640000

ヒント

メソッドの中では、自分のフィールドをそのまま使える。

模範解答
public class Main {

    static class Product {
        String name;
        int price;
        int stock;

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

        int stockValue() {
            return price * stock;
        }
    }

    public static void main(String[] args) {
        Product p = new Product("ノートPC", 128000, 5);

        System.out.println(p.stockValue());
    }
}
解説

メソッドの中では this を省略できるprice * stockthis.price * this.stock と同じ意味になる。

JV08-D04 自分自身を指すキーワードを補ってコードを完成させよ。 穴埋め★☆☆無料
コード
public class Main {

    static class Item {
        String name;

        Item(String name) {
            ____.name = name;
        }
    }

    public static void main(String[] args) {
        System.out.println(new Item("テスト").name);
    }
}
期待される結果

出力 == テスト

ヒント

「これ・自分自身」を意味する4文字。

模範解答
this
解説

引数とフィールドが同じ名前のとき、this で区別する。書かないと引数に引数を代入するだけになり、フィールドが空のままになる。

JV08-D05 toString() を定義しないオブジェクトを println するとどうなるか。
A. エラー B. クラス名と英数字が表示される C. null D. 空行
選択★☆☆無料
期待される結果

解答 == B

ヒント

Product@1b6d3586 のような表示になる。

模範解答
B
解説

メモリ上の位置が表示されるので、人間には意味が読み取れない。toString() を定義して、読める形にするのが実務の作法。

JV08-D06 Product クラスに toString() を定義し、「ノートPC(128,000円)」の形式で表示されるようにせよ(オブジェクトをそのまま println すること)。 コーディング★★☆広告解放
期待される結果

出力 == ノートPC(128,000円)

ヒント

@Override public String toString() { return ...; } と書く。

模範解答
public class Main {

    static class Product {
        String name;
        int price;

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

        @Override
        public String toString() {
            return name + "(" + String.format("%,d", price) + "円)";
        }
    }

    public static void main(String[] args) {
        Product p = new Product("ノートPC", 128000);

        System.out.println(p);
    }
}
解説

println にオブジェクトを渡すと toString() が自動で呼ばれる。デバッグや一覧表示のとき、この定義があると格段に楽になる。

JV08-D07 オブジェクトを作るキーワードを補ってコードを完成させよ。 穴埋め★★☆広告解放
コード
public class Main {

    static class Item {
        int value;
        Item(int value) { this.value = value; }
    }

    public static void main(String[] args) {
        Item i = ____ Item(42);

        System.out.println(i.value);
    }
}
期待される結果

出力 == 42

ヒント

「新しく作る」を意味する3文字。

模範解答
new
解説

new でメモリ上に実物が作られる。クラスは設計図にすぎず、new しない限り実体は存在しない。

JV08-D08 Product b = a; のあと b.price = 999; とすると a.price はどうなるか。
A. 変わらない B. 999になる C. エラー D. 0になる
選択★★☆広告解放
期待される結果

解答 == B

ヒント

オブジェクトも配列と同じく参照(JV06)。

模範解答
B
解説

オブジェクトも参照が渡される。同じ実物を2つの名前で指しているだけなので、片方を変えればもう片方も変わる。

JV08-D09 オブジェクトの配列を作れ(Product を2つ:ノートPC/128000/5、マウス/3200/42)。在庫金額の合計を出力すること。 コーディング★★☆広告解放
期待される結果

出力 == 774400

ヒント

Product[] items = { new Product(...), new Product(...) }; で作れる。

模範解答
public class Main {

    static class Product {
        String name;
        int price;
        int stock;

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

        int stockValue() {
            return price * stock;
        }
    }

    public static void main(String[] args) {
        Product[] items = {
            new Product("ノートPC", 128000, 5),
            new Product("マウス", 3200, 42),
        };

        int total = 0;
        for (Product item : items) {
            total += item.stockValue();
        }

        System.out.println(total);
    }
}
解説

配列が1つで済む。JV06では3つの配列を添字で対応させていたが、今は名前・価格・在庫が常に一体で動く。

JV08-D10 Product クラスに判定メソッド label() を追加せよ(在庫0なら「品切れ」、10未満なら「残りわずか」、それ以外「在庫あり」)。在庫5で試すこと。 コーディング★★☆広告解放
期待される結果

出力 == 残りわずか

ヒント

早期リターンで書くと読みやすい。

模範解答
public class Main {

    static class Product {
        String name;
        int stock;

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

        String label() {
            if (stock == 0) return "品切れ";
            if (stock < 10) return "残りわずか";
            return "在庫あり";
        }
    }

    public static void main(String[] args) {
        Product p = new Product("ノートPC", 5);

        System.out.println(p.label());
    }
}
解説

判定ロジックを商品自身に持たせている。JV07では引数で在庫を渡していたが、商品が自分の在庫を知っているのだから、自分で判定できるほうが自然。

4実践シナリオ(5問)

JV08-S01 【顧客クラス】Customer クラスを作れ(name・area・points)。「田中商事(東京)1,500pt」の形式で表示する toString を定義し、1件作って出力すること。 コーディング★★☆広告解放
期待される結果

出力 == 田中商事(東京)1,500pt

ヒント

toString の中で String.format("%,d", points) を使う。

模範解答
public class Main {

    static class Customer {
        String name;
        String area;
        int points;

        Customer(String name, String area, int points) {
            this.name = name;
            this.area = area;
            this.points = points;
        }

        @Override
        public String toString() {
            return name + "(" + area + ")"
                    + String.format("%,d", points) + "pt";
        }
    }

    public static void main(String[] args) {
        Customer c = new Customer("田中商事", "東京", 1500);

        System.out.println(c);
    }
}
解説

表示の形をクラス自身が知っている。使う側は println するだけでよく、書式を組み立てる手間がなくなる。

JV08-S02 【商品一覧】Product の配列(3件)を作り、「1. ノートPC:640,000円」の形式で3行出力せよ(ノートPC/128000/5、マウス/3200/42、モニター/45000/0)。 コーディング★★☆広告解放
期待される結果

出力3行

ヒント

stockValue() メソッドを使う。番号は添字+1。

模範解答
public class Main {

    static class Product {
        String name;
        int price;
        int stock;

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

        int stockValue() {
            return price * stock;
        }
    }

    public static void main(String[] args) {
        Product[] items = {
            new Product("ノートPC", 128000, 5),
            new Product("マウス", 3200, 42),
            new Product("モニター", 45000, 0),
        };

        for (int i = 0; i < items.length; i++) {
            System.out.println((i + 1) + ". " + items[i].name + ":"
                    + String.format("%,d", items[i].stockValue()) + "円");
        }
    }
}
解説

データがずれる心配がない。JV06の3配列方式では、1つの配列に追加し忘れると破綻したが、この形なら起こりえない。

JV08-S03 【オブジェクトも参照】Product を2つの変数で指し、片方を変更してもう片方も変わることを確認せよ(変更後の元の価格を出力)。 コーディング★★☆広告解放
期待される結果

出力 == 999

ヒント

Product b = a; のあと b.price を変更する。

模範解答
public class Main {

    static class Product {
        int price;
        Product(int price) { this.price = price; }
    }

    public static void main(String[] args) {
        Product a = new Product(100);
        Product b = a;

        b.price = 999;

        System.out.println(a.price);
    }
}
解説

配列と同じく、オブジェクトも参照。「コピーしたつもりが元も変わった」という不具合の原因になるので、常に意識しておく。

JV08-S04 【注文クラス】Order クラスを作れ(product:Product型、qty:int)。クラスの中に別のクラスを持たせ、金額を返す amount() を定義して「ノートPC × 2 = 256,000円」と出力すること。 コーディング★★★広告解放
期待される結果

出力 == ノートPC × 2 = 256,000円

ヒント

Order のフィールドに Product 型を持たせる。amount() は product.price * qty。

模範解答
public class Main {

    static class Product {
        String name;
        int price;

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

    static class Order {
        Product product;
        int qty;

        Order(Product product, int qty) {
            this.product = product;
            this.qty = qty;
        }

        int amount() {
            return product.price * qty;
        }

        @Override
        public String toString() {
            return product.name + " × " + qty + " = "
                    + String.format("%,d", amount()) + "円";
        }
    }

    public static void main(String[] args) {
        Product p = new Product("ノートPC", 128000);
        Order o = new Order(p, 2);

        System.out.println(o);
    }
}
解説

オブジェクトの中にオブジェクトを持てる。「注文には商品が含まれる」という現実の関係が、そのままコードの構造になっている。

JV08-S05 【集計メソッド】Product 配列を受け取って在庫総額を返す static メソッド totalValue を作れ(3件で試し、「927,400円」と出力)。データ:ノートPC/128000/5、マウス/3200/42、キーボード/8500/18。 コーディング★★★広告解放
期待される結果

出力 == 927,400円

ヒント

static int totalValue(Product[] items) の形。中で stockValue() を呼ぶ。

模範解答
public class Main {

    static class Product {
        String name;
        int price;
        int stock;

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

        int stockValue() {
            return price * stock;
        }
    }

    static int totalValue(Product[] items) {
        int total = 0;
        for (Product item : items) {
            total += item.stockValue();
        }
        return total;
    }

    public static void main(String[] args) {
        Product[] items = {
            new Product("ノートPC", 128000, 5),
            new Product("マウス", 3200, 42),
            new Product("キーボード", 8500, 18),
        };

        System.out.println(String.format("%,d", totalValue(items)) + "円");
    }
}
解説

引数が1つで済む。JV07では names と values の2つを渡していたが、Product 型なら1つの配列にすべての情報が入っている。

5仕上げ課題

JV08-FINAL 【クラスで書き直す在庫管理】JV07の在庫レポートを、クラスを使って書き直せ。

Product クラス
・フィールド:name(String)、price(int)、stock(int)
・コンストラクタ:3つの値を受け取る
int stockValue() … 在庫金額(price × stock)
String label() … 0なら「品切れ」、10未満なら「残りわずか」、それ以外「在庫あり」
toString() … 「ノートPC:640,000円(残りわずか)」の形式

Main クラスの static メソッド
int totalValue(Product[] items) … 在庫総額
Product maxItem(Product[] items) … 在庫金額が最大の商品オブジェクトを返す

データ:ノートPC/128000/5、マウス/3200/42、モニター/45000/0、キーボード/8500/18

期待される出力(6行)
1. ノートPC:640,000円(残りわずか)
2. マウス:134,400円(在庫あり)
3. モニター:0円(品切れ)
4. キーボード:153,000円(在庫あり)
---
在庫総額:927,400円(最高:ノートPC)
総合★★★広告解放
期待される結果

出力6行が完全一致

ヒント

toString に整形を任せれば、出力は (i+1) + ". " + items[i] だけで済む。maxItem は Product 型を返すので、呼び出し側で .name を取り出せる。

模範解答
public class Main {

    static class Product {
        String name;
        int price;
        int stock;

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

        int stockValue() {
            return price * stock;
        }

        String label() {
            if (stock == 0) return "品切れ";
            if (stock < 10) return "残りわずか";
            return "在庫あり";
        }

        @Override
        public String toString() {
            return name + ":" + String.format("%,d", stockValue())
                    + "円(" + label() + ")";
        }
    }

    static int totalValue(Product[] items) {
        int total = 0;
        for (Product item : items) {
            total += item.stockValue();
        }
        return total;
    }

    static Product maxItem(Product[] items) {
        Product max = items[0];
        for (Product item : items) {
            if (item.stockValue() > max.stockValue()) {
                max = item;
            }
        }
        return max;
    }

    public static void main(String[] args) {
        Product[] items = {
            new Product("ノートPC", 128000, 5),
            new Product("マウス", 3200, 42),
            new Product("モニター", 45000, 0),
            new Product("キーボード", 8500, 18),
        };

        for (int i = 0; i < items.length; i++) {
            System.out.println((i + 1) + ". " + items[i]);
        }

        System.out.println("---");
        System.out.println("在庫総額:" + String.format("%,d", totalValue(items))
                + "円(最高:" + maxItem(items).name + ")");
    }
}
解説

JV06からJV08まで、同じ在庫レポートを3回書いてきた。その変化を振り返ってほしい。

JV06では3つの配列を添字で対応させ、main にすべての処理が詰まっていた。JV07でメソッドに分けて main は短くなったが、maxName(names, values) のように関連するデータを別々に渡す不自然さが残っていた。そしてJV08で、ようやく「商品」という単位でデータと処理がまとまった。

最も大きな変化は maxItem の戻り値だ。JV07では名前という文字列しか返せなかったが、今は商品オブジェクトそのものを返せる。だから呼び出し側で .name でも .price でも、必要な情報を後から取り出せる。「一番売れた商品の在庫数も知りたい」と言われても、メソッドを直す必要がない。

出力部分も (i + 1) + ". " + items[i] だけになった。toString() が整形を引き受けているので、使う側は「何を表示するか」だけ考えればよい

これがオブジェクト指向の出発点だ。データと、そのデータに対する処理を同じ場所に置く——たったそれだけの原則が、プログラムの構造を大きく変える。

ただし今の Product には弱点がある。フィールドが外から自由に書き換えられるので、p.stock = -100; のような不正な値も入れられてしまう。次章のカプセル化で、この穴を塞ぐ。