「Javaで自動販売機を作ってみたいけど、どのクラスをどう分ければいいかわからない」。そんな声に応えるのがこの記事です。
Item(商品)/VendingMachine(自販機本体)/Main(実行)という3クラス設計で動く自動販売機プログラムを、4ステップで作る方法を解説します。
コピペで30分で完成し、オブジェクト指向のカプセル化・クラス分割の意義まで実践的に身につきます。
結論から言うと、自動販売機プログラムは「商品(Item)」「本体(VendingMachine)」「実行(Main)」の3クラスに分け、お金の投入→商品選択→在庫チェック→お釣り計算という一連の処理をメソッドとして実装すれば完成します。
以降で1つずつ手順を追って実装していきます。
私の体験談|「車はクラス」が腑に落ちなかった話
Javaを学び始めた頃、参考書の「車はクラス、走るはメソッド」という例えがどうしても腑に落ちず、「車じゃなくて動くシステムが作りたいんだよ」と心でツッコんでいました。
状況が変わったのは、自販機のような入力・状態・出力が1つのオブジェクトに収まる小さなシステムを自分で実装したとき。
「Itemクラスが在庫を持ち、VendingMachineが販売を担当する」と分割した瞬間、教科書のカプセル化が一気に実感に変わりました。
この記事の自販機題材はその追体験を最短でしてもらうための構成です。
この記事を読めば:
- 3クラス設計の役割分担(Item/VendingMachine/Main)が腹落ちする
- コピペで30分でお金投入・商品選択・お釣り計算が動くプログラムが完成する
- オブジェクト指向の「カプセル化」「責務分担」「クラス設計の判断基準」を実例で理解できる
私自身もJavaを学び始めた頃に自動販売機プログラムを作成し、オブジェクト指向への理解が一気に深まりました。
自動販売機はクラス、オブジェクト、カプセル化など基本概念を一通り練習できる最適な題材です。
Javaの基本文法(変数、条件分岐、繰り返しなど)を学んだ方が次のステップとして取り組むのにぴったりの内容です。
※この記事のコードはJava 11以降で動作します。
2026年7月時点の最新LTSはJava 25(2025年9月16日リリース)で、premier supportは2030年9月まで提供されます。
Java 26(2026年3月リリース)は非LTSのため、新規プロジェクトでは安定LTSのJava 21またはJava 25の使用を推奨します。
PARTS LIST /目次
Javaで作る自動販売機の完成イメージ|3クラスで何が動くか

自動販売機プログラムとは、商品の登録・入金の受付・購入可否の判定・お釣りの計算という一連の処理をコードで再現するプログラムです。
現実の自動販売機が持つ「状態(在庫・投入金額)」と「振る舞い(販売・返金)」をクラスというひとまとまりの単位に落とし込む練習として、オブジェクト指向学習の定番教材になっています。
この記事で作成を目指すのは、コンソール上で動作するシンプルなテキストベースの自動販売機です。
具体的には、以下のような機能を持つものを想定しています。
ポイント
- 商品リストの表示: 自動販売機が販売している商品名と価格の一覧を表示します。
- お金の投入: ユーザーが指定した金額(例えば10円、50円、100円、500円、1,000円)を投入できます。投入された合計金額を表示します。
- 商品の選択: ユーザーが購入したい商品を選択します。
- 購入処理: 投入金額が商品価格以上か、商品の在庫があるかを確認します。購入可能な場合は、投入金額から商品価格を引きます。在庫を1つ減らします。
- お釣りの計算と表示: 購入後、お釣りがあれば計算して表示します。
- エラーメッセージ: お金が足りない場合や、在庫がない場合に適切なメッセージを表示します。
自動販売機の処理フロー|起動から終了までの5ステップ
実装前に、プログラムの処理の流れを確認しておきましょう。全体像を把握しておくと、各ステップのコードの意味が理解しやすくなります。
- 起動 → 商品リストを初期化し、メニューを表示する
- 操作選択 → ①お金を投入 / ②商品を購入 / ③終了(お釣り返却) のいずれかを選ぶ
- お金の投入 → 受け付け可能な金種(10/50/100/500/1000円)かチェックし、投入金額に加算
- 商品の購入 → 商品番号を入力 → 在庫チェック → 金額チェック → 購入処理・在庫減算・お釣り返却
- 終了 → 残金があればお釣りとして返却し、総売上を表示してプログラム終了
このフローを「商品(Item)」「自動販売機本体(VendingMachine)」「実行クラス(Main)」の3つのクラスで実装していきます。
Javaで自動販売機を作る4ステップ|3クラス設計で実装する
それでは、実際にJavaプログラミングで自動販売機を作成していく手順を、4つのステップに分けて解説します。
自動販売機のクラス設計|Item・VendingMachine・Mainの役割
プログラムを作る前に、自動販売機を構成する要素を洗い出し、洗い出した要素をどのようにクラスとして表現するかを設計します。オブジェクト指向プログラミングでは、現実世界の「モノ」や「概念」を「クラス」としてモデル化します。今回は以下の要素を洗い出し、Item・VendingMachine・Mainの3つのクラスに分けて実装します。お金(Money)は専用クラスにせず、VendingMachineの int 変数で表現します。
以下のように、まず「何が必要か」を考え、それぞれの役割と情報(属性)、そしてどのような操作(振る舞い)ができるかを整理するのがクラス設計の第一歩です。
商品 (Item)
- 属性(データ): 商品名 (name), 価格 (price), 在庫数 (stock)
- 振る舞い(メソッド): 商品情報を取得する、在庫を減らす など
お金 (Money)
今回はシンプルに、投入された合計金額を管理する変数で表現します。より詳細にするなら、硬貨や紙幣の種類と枚数を管理するクラスも考えられます。
自動販売機本体 (VendingMachine)
- 属性(データ): 商品リスト (items), 投入金額 (currentAmount), 売り上げ (sales) など
- 振る舞い(メソッド): お金を受け取る (insertMoney), 商品を表示する (displayItems), 商品を購入する (purchaseItem), お釣りを返す (returnChange) など
なぜ3つのクラスに分けるのか|OOPでの責務分担
クラスを分ける本当の価値は、「仕様の明確化」と「機能の部品化」の2点にあります。
1つのクラスに全ロジックを書くと、商品の在庫管理を直したいだけなのに購入処理や入力チェックまで読まないと安全に変更できません。
もっとも、この記事の自販機くらいの規模ではまだ実感は薄いだろう。
実務では、数千行のif-elseが延々と連なる「モンスターメソッド」を保守した経験がある。
何か一つ機能を追加するたびに全条件分岐を目で追う必要があり、触るたびにチーム全員がうんざりしていた。
ポリモーフィズムを使って責務ごとにクラスを分け直したところ、追加・修正にかかるコストはおよそ1/5になった。
クラスを分ける・分けないの差は、コードが小さいうちは見えず、育ったコードになって初めて数字として現れる。
この記事の3クラス構成は、オブジェクト指向の単一責任の原則(Single Responsibility Principle)を素直にあてはめたものです。
- Item: 「商品の状態(名前・価格・在庫)」だけを持つ
- VendingMachine: 「販売の振る舞い(投入・選択・お釣り・売上)」を担当
- Main: 「実行の起点(プログラムを起動してVendingMachineに任せる)」だけを担当
商品・入金管理・自販機本体・実行の4クラスに分ける構成もありますが、初学者向けの第1段階としては「お金はint変数で管理する3クラス構成」で十分です。
仕様が増えて硬貨の枚数管理・複数自販機の同時稼働などが必要になったら、その時点でMoneyクラスを切り出して4クラス構成に進化させるのが、現場でも使われるリファクタリングの定石です。
クラスを分けるかどうか迷ったら、「変更理由が違う処理かどうか」を判断基準にすると失敗が減ります。
【現場視点での補足】OOP入門書では「継承で共通化」と教わりますが、実務の現場では継承よりコンポジション(クラスを組み合わせて使う)が好まれる場面が増えています。
この記事の「VendingMachineがItemをListで持つ」設計はまさにコンポジションで、継承の親クラス追跡で処理を見失う罠を避けられる構成です。
教科書通りのOOPと現場のOOPに少しズレがあることを、最初の題材で体感しておくと後の学習がラクになります。
Itemクラス(商品)の実装|カプセル化とgetterの書き方
設計に基づき、まずは「商品」を表すItemクラスを作成します。
Itemという名前の新しいクラスファイルを作成し、以下のようなコードを記述します。
public class Item {
private String name; // 商品名 (カプセル化のためprivate)
private int price; // 価格 (カプセル化のためprivate)
private int stock; // 在庫数 (カプセル化のためprivate)
// コンストラクタ: Itemオブジェクト生成時に名前、価格、在庫数を設定する
public Item(String name, int price, int stock) {
this.name = name;
this.price = price;
this.stock = stock;
}
// 商品名を取得するメソッド (getter)
public String getName() {
return name;
}
// 価格を取得するメソッド (getter)
public int getPrice() {
return price;
}
// 在庫数を取得するメソッド (getter)
public int getStock() {
return stock;
}
// 在庫があるかどうかを確認するメソッド
public boolean isAvailable() {
return stock > 0;
}
// 在庫を1つ減らすメソッド
public void decreaseStock() {
if (stock > 0) {
stock--;
}
}
// 商品情報を文字列で返すメソッド (表示用)
@Override
public String toString() {
return name + " (" + price + "円) - 在庫: " + (stock > 0 ? stock + "個" : "売り切れ");
}
}
今回はシンプルにするため、「お金」専用のクラスは作成せず、自動販売機クラス内で投入金額を int 型の変数で管理することにします。
ポイント
private String name;のように、フィールド(属性)をprivateにしています。このprivate修飾子はカプセル化というオブジェクト指向の重要な考え方で、クラスの内部データを外部から直接アクセスできないように保護するためです。- データにアクセスするためには、
public String getName()のようなpublicなメソッド(getter)を用意します。 public Item(String name, int price, int stock)は コンストラクタ と呼ばれ、Itemオブジェクトが作られるときに自動的に呼び出され、初期値を設定します。isAvailable()やdecreaseStock()は、商品の状態を確認したり、操作したりするためのメソッド(振る舞い)です。toString()メソッドは、オブジェクトを文字列として表現したいときに使われます。System.out.println()などでItemオブジェクトを直接表示しようとすると、このメソッドが呼び出されます。
「たかがフィールドをprivateにするだけ」と流し読みしそうになるが、これを徹底していなかったせいで実際に事故を見たことがある。
以前、銀行口座系のドメインを扱うコードを引き継いだとき、残高を保持するフィールドがpublicのままで、別チームのメンバーが値を直接書き換えるコードを平然と書いていた。
原因を追うだけで数日かかる不整合だった。privateにしてgetter/setter経由のアクセスに統一してからは、同種のバグは0件になっている。
自販機のItemクラス程度の小さな題材では実感しにくいが、カプセル化は「お作法」ではなく、育ったコードで実際に事故を防ぐ仕組みだ。
VendingMachineクラスの実装|状態管理と購入処理を1クラスに集約
次に、自動販売機の本体機能を持つVendingMachineクラスを作成します。
VendingMachine という名前で新しいクラスファイルを作成し、以下のコードを記述します。
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner; // ユーザー入力を受け取るために必要
public class VendingMachine {
private List<Item> items; // 商品リスト (Itemオブジェクトを格納)
private int currentAmount; // 現在の投入金額
private int sales; // 総売上
// コンストラクタ
public VendingMachine() {
items = new ArrayList<>(); // 空の商品リストを作成
currentAmount = 0;
sales = 0;
// 初期商品をいくつか追加しておく
initializeItems();
}
// 初期商品を設定するメソッド
private void initializeItems() {
items.add(new Item("お茶", 120, 5));
items.add(new Item("コーヒー", 130, 3));
items.add(new Item("コーラ", 150, 0)); // 在庫切れの商品
items.add(new Item("水", 100, 10));
}
// お金を投入するメソッド
public void insertMoney(int amount) {
// 対応する金種かチェック (例: 10, 50, 100, 500, 1000円のみ受け付ける)
if (amount == 10 || amount == 50 || amount == 100 || amount == 500 || amount == 1000) {
currentAmount += amount;
System.out.println(amount + "円投入されました。現在の投入金額: " + currentAmount + "円");
} else {
System.out.println(amount + "円は投入できません。10円、50円、100円、500円、1,000円のみ利用可能です。");
}
}
// 現在の投入金額を取得するメソッド
public int getCurrentAmount() {
return currentAmount;
}
// 商品リストを表示するメソッド
public void displayItems() {
System.out.println("--- 商品リスト ---");
if (items.isEmpty()) {
System.out.println("現在、販売中の商品はありません。");
return;
}
for (int i = 0; i < items.size(); i++) {
System.out.println((i + 1) + ". " + items.get(i).toString()); // ItemクラスのtoString()が呼ばれる
}
System.out.println("------------------");
}
// 商品を購入するメソッド
public void purchaseItem(int itemIndex) {
// ユーザーが入力するのは1から始まる番号なので、リストのインデックスに変換 (0から始まる)
int actualIndex = itemIndex - 1;
// 有効な商品番号かチェック
if (actualIndex < 0 || actualIndex >= items.size()) {
System.out.println("無効な商品番号です。");
return;
}
Item selectedItem = items.get(actualIndex);
// 在庫チェック
if (!selectedItem.isAvailable()) {
System.out.println("申し訳ありません。" + selectedItem.getName() + "は売り切れです。");
return;
}
// 金額チェック
if (currentAmount < selectedItem.getPrice()) {
System.out.println("投入金額が不足しています。" + selectedItem.getName() + "の価格は" + selectedItem.getPrice() + "円です。");
return;
}
// 購入処理
currentAmount -= selectedItem.getPrice(); // 金額を減らす
selectedItem.decreaseStock(); // 在庫を減らす
sales += selectedItem.getPrice(); // 売上を計上する
System.out.println(selectedItem.getName() + "を購入しました。");
// お釣り処理
returnChange();
}
// お釣りを返すメソッド
public void returnChange() {
if (currentAmount > 0) {
System.out.println("お釣り " + currentAmount + "円をお返しします。");
currentAmount = 0; // 投入金額をリセット
} else {
System.out.println("お釣りはありません。");
}
System.out.println("ご利用ありがとうございました。");
}
// 自動販売機を操作するメインのロジック (例)
public void run() {
Scanner scanner = new Scanner(System.in); // ユーザー入力用
int choice = -1;
while (choice != 0) {
System.out.println("\n現在の投入金額: " + currentAmount + "円");
displayItems();
System.out.println("操作を選んでください:");
System.out.println("1: お金を投入する");
System.out.println("2: 商品を購入する");
System.out.println("3: お釣りを返却して終了する");
System.out.print("番号を入力: ");
// 入力が数値でない場合の対策
if (scanner.hasNextInt()) {
choice = scanner.nextInt();
} else {
System.out.println("無効な入力です。数字を入力してください。");
scanner.next(); // 不正な入力を読み飛ばす
continue; // ループの先頭に戻る
}
switch (choice) {
case 1:
System.out.print("投入する金額を入力してください (10, 50, 100, 500, 1000): ");
if (scanner.hasNextInt()) {
int amount = scanner.nextInt();
insertMoney(amount);
} else {
System.out.println("無効な入力です。数字を入力してください。");
scanner.next(); // 不正な入力を読み飛ばす
}
break;
case 2:
if (currentAmount <= 0) {
System.out.println("先にお金を投入してください。");
break;
}
System.out.print("購入したい商品の番号を入力してください: ");
if (scanner.hasNextInt()) {
int itemNumber = scanner.nextInt();
purchaseItem(itemNumber);
} else {
System.out.println("無効な入力です。数字を入力してください。");
scanner.next(); // 不正な入力を読み飛ばす
}
break;
case 3:
returnChange();
choice = 0; // ループを抜けるためにchoiceを0にする
break;
default:
System.out.println("無効な選択です。1から3の番号を入力してください。");
}
}
scanner.close(); // Scannerを閉じる
System.out.println("自動販売機プログラムを終了します。総売上: " + sales + "円");
}
}
ポイント
import java.util.ArrayList;など:他のクラス(ここではリストやスキャナー)を利用するためにimport文が必要です。private List<Item> items;:Itemオブジェクトを複数格納するためにList(具体的にはArrayList)を使っています。これにより、商品の追加や削除が容易になります。initializeItems(): 最初にいくつかの商品をリストに追加しています。insertMoney(): 投入された金額をcurrentAmountに加算します。ここでは簡単な金種チェックも入れています。displayItems():itemsリストの中身を順番に表示します。items.get(i).toString()で、各ItemオブジェクトのtoString()メソッドが呼び出され、整形された文字列が表示されます。purchaseItem(): 商品番号を受け取り、在庫と投入金額をチェックして購入処理を行います。問題なければ金額と在庫を更新し、returnChange()を呼び出します。returnChange(): 残っているcurrentAmountをお釣りとして表示し、0にリセットします。run(): ユーザーとの対話(メニュー表示、入力受付、メソッド呼び出し)を行う中心的なメソッドです。Scannerを使ってコンソールからの入力を受け取ります。whileループとswitch文を使って、ユーザーが終了を選ぶまで操作を繰り返せるようにしています。入力が数値でない場合のエラー処理も簡単に追加しました。
Mainクラスで動作テスト|main()メソッドから実行する
最後に、作成したVendingMachineクラスを実際に動かしてみるための実行用クラスを作成し、動作を確認します。
Main (または VendingMachineTest など分かりやすい名前) という名前で新しいクラスファイルを作成し、main メソッドを記述します。
main メソッドは、Javaプログラムの実行開始点となる特別なメソッドです。
public class Main {
public static void main(String[] args) {
// VendingMachineクラスのインスタンス(実体)を作成
VendingMachine myVendingMachine = new VendingMachine();
// 自動販売機を実行
myVendingMachine.run();
}
}
ポイント
public static void main(String[] args): この定型文がmainメソッドの定義です。プログラムはここから実行が始まります。VendingMachine myVendingMachine = new VendingMachine();:VendingMachineクラスの設計図をもとに、myVendingMachineという名前の実際の自動販売機オブジェクト(インスタンス)を作成しています。new VendingMachine()でコンストラクタが呼び出され、初期化が行われます。myVendingMachine.run();: 作成した自動販売機オブジェクトのrun()メソッドを呼び出し、ユーザーが操作できる状態にします。
想定される出力例|コンソールに何が出れば成功か
うまく動けば、コンソールには以下のような出力が表示されます。動作確認の目安にしてください。
※出力例は「完成コード一覧」のコードの表示です。手順の run() は同じメニューを3行に分けて表示します。
現在の投入金額: 0円
--- 商品リスト ---
1. お茶 (120円) - 在庫: 5個
2. コーヒー (130円) - 在庫: 3個
3. コーラ (150円) - 在庫: 売り切れ
4. 水 (100円) - 在庫: 10個
------------------
操作を選んでください: 1:お金投入 / 2:商品購入 / 3:終了
番号を入力: 1
投入する金額を入力してください (10, 50, 100, 500, 1000): 500
500円投入されました。現在の投入金額: 500円
(中略)
購入したい商品の番号を入力してください: 1
お茶を購入しました。
お釣り 380円をお返しします。
ご利用ありがとうございました。
(3を入力して終了。残金が0円なら「お釣りはありません。」「ご利用ありがとうございました。」も表示される)
自動販売機プログラムを終了します。総売上: 120円
実行とデバッグのチェックリスト
- 表示される指示に従って、お金を投入したり、商品を選択したりしてみてください。
- 意図した通りに動作するか確認します。
- お金は正しく加算されるか?
- 商品は正しく表示されるか?
- 購入処理は正しく行われるか?(金額、在庫)
- お釣りは正しいか?
- 在庫切れの商品を買おうとしたらどうなるか?
- お金が足りない場合はどうなるか?
- 不正な金額や商品番号を入力したらどうなるか?
※この記事の動作確認スクリーンショットは2025年4月にmacOS+OpenJDK 21で取得したものです。
Java 21〜25いずれでも同様の出力が得られます。

これでJavaプログラミングによる基本的な自動販売機が完成しました。
つまずきポイント
実装中によくある落とし穴を2つ紹介します。
- 商品番号のインデックスずれ: ユーザーが入力するのは1始まりの番号ですが、Javaのリストは0始まりです。
purchaseItem()内でint actualIndex = itemIndex - 1;と変換しているので、この変換を忘れると意図しない商品が選ばれてしまいます。 - Scannerを閉じ忘れる:
scanner.close()を呼ばないと、IDEがリソースリークの警告を出します。プログラム終了前に呼び出す習慣をつけましょう。この記事のコードではrun()メソッドの末尾で閉じています。なお、Java 7以降ではtry (Scanner scanner = new Scanner(System.in)) { ... }のように try-with-resources 構文を使うと自動的に閉じられるため、より安全です。 - 「Money専用クラスは作らないの?」と迷う罠: 設計章で「お金 (Money)」と並べておきながら本実装ではint変数で済ませているため、初学者は「サボったのでは?」と感じがちです。ですがこの記事はあえて「最初から完璧な設計を目指さず、必要になったら切り出す」方針を取っています。仕様が増えてMoneyクラスを切り出す瞬間に、はじめてクラス分割の意義が体に入る。これが私の体感です。
Scannerクラスの詳しい使い方はこちらも参考にしてください。
Javaで自動販売機を作るときによくある質問【FAQ】
クラス設計・Money専用クラスの要否・Scannerのクローズ処理など、実装時につまずきやすい5つの疑問に答えます。
Q1. なぜItemクラスとVendingMachineクラスに分けるの?1ファイルでもいい?
A1. 1ファイルでも動きますが、商品の追加・在庫管理ロジックが自販機本体の処理と混ざって保守性が落ちます。Itemに「商品の状態」、VendingMachineに「販売の振る舞い」と責務を分けておくと、後でCSV読み込みやGUI化に拡張するときに変更箇所が局所化されます。オブジェクト指向設計で基本とされる「単一責任の原則(SRP)」を体感する練習になります。
Q2. お金専用クラス(Money)は作らなくていい?
A2. 初学者向けの教材としては不要です。投入金額はint型変数で管理すれば十分動き、この記事もその構成にしています。硬貨の枚数管理・釣銭切れの検出など仕様を増やす段階で初めてMoneyクラスを切り出すと、クラス分割の判断基準(変更理由が違うか)を体感できます。
Q3. Scannerを閉じ忘れるとどうなる?
A3. 閉じ忘れても、このコードのように標準入力(System.in)を読むだけのScannerでは実行時にエラーや警告は出ません。ただしIDE(Eclipseなど)は「リソースリーク」の警告を表示するため、close()を呼ぶかtry-with-resourcesを使う習慣をつけておくと安心です。なおScannerを閉じるとSystem.inも閉じられるため、閉じたあとに同じプログラム内で標準入力を読み直すことはできません。この記事のコードではrun()メソッドの末尾でscanner.close()を呼び出しています。Java 7以降ではtry (Scanner scanner = new Scanner(System.in)) { ... }のtry-with-resources構文を使うと、ブロックを抜けたタイミングで自動クローズされるためより安全です。
Q4. 在庫切れの商品を選んだときの挙動は?
A4. purchaseItem()メソッド内でselectedItem.isAvailable()を呼び、在庫0なら「申し訳ありません。○○は売り切れです。」とメッセージを出して購入処理を中断します。投入金額はそのまま残るので、別の商品を選び直すか終了でお釣りとして返却されます。
Q5. このコードはJavaのどのバージョンから動く?
A5. Java 11以降で動作確認済みです。Listのダイヤモンド演算子・拡張for文など使っているのは古い文法のみのため、Java 8でも問題なく動きます。新規プロジェクトで学習する場合は2026年7月時点の最新LTSであるJava 25(2025年9月16日リリース、premier support 2030年9月まで)または安定LTSのJava 21の使用を推奨します。
発展課題|CSV読込・Moneyクラス分離・例外処理・GUI化で実力を伸ばす
基本的な自動販売機が完成したら、次のステップとして以下の発展課題に挑戦してみましょう。
- CSVから商品データを読み込む:
initializeItems()でハードコードしている商品を、CSVファイルから読み込む形式にするとファイルI/Oの練習になります。 - 硬貨の種類別管理クラスを追加する: 投入金額を合計値で管理するだけでなく、10円玉・50円玉・100円玉それぞれの枚数を管理する
Moneyクラスを作ると、より本格的なオブジェクト設計の練習になります。 - 例外処理を本格実装する: 無効な商品番号や負の金額投入に対して
IllegalArgumentExceptionなどをスローする実装にすることで、例外処理の理解が深まります。 - HashMapを使った実装に書き換える: この記事では
ArrayList<Item>で商品を管理しましたが、HashMap<String, Integer>で商品名と在庫だけ管理する簡易版もあります。 - GUIアプリ化する(JavaFX / Swing): コンソールベースからGUIベースに移行するとイベント駆動プログラミングを学べます。
私のおすすめの学習ステップ|「写経→改造→自作」の3段階
OOPの本を何冊読んでも、自分でクラス設計しようとすると手が止まる。これは多くの学習者が通る道です。
私が遠回りせず身につけられたのは「写経→改造→自作」の3ステップでした。
- 写経: この記事のコードを何も見ずに同じ動作になるまで写経。これだけで
ScannerやArrayListの使い所が手に馴染みます。 - 改造: 「商品を5つに増やす」「ポイントカード機能を追加」など小さな変更を3つ加える。VendingMachineクラスを変更すれば済むのか、Itemクラスにメソッドを足すべきなのかを毎回考えるのが筋トレです。
- 自作: 最後に「コインロッカー」「貸出管理システム」など別ドメインで同じ3クラス構成を1から書く。ここまで来て初めて「クラス分けが手癖になった」状態になります。
Java自動販売機のクラス構成パターン|1クラス・3クラス・5クラス構成の違い
自動販売機プログラムは、ネット上のサンプルによってクラスの分け方がまちまちです。
どれが正解というより、仕様の複雑さに応じて選ぶべき粒度が変わると捉えると迷いにくくなります。
ここまで実装した3クラス構成が、なぜその位置づけなのかを整理しておきます。
この記事があえて3クラス構成を選んでいるのは、「最初から完璧な設計を目指さず、必要になったら切り出す」という前述の方針のためです。
1クラス構成から書き始めた人はまずこの記事の3クラス構成を、既に3クラス構成が板についた人は次の発展課題(Moneyクラス分離・CSV読込)を通じて5クラス構成側へ進む、という流れで捉えると学習の順番に迷いません。
完成コード一覧(3ファイル構成)
このプログラムは以下の3ファイル構成です。それぞれのコードをそのまま使えます。
同じディレクトリに配置して javac *.java でコンパイルし、java Main で実行してください。
Item.java(商品クラス)
public class Item {
private String name;
private int price;
private int stock;
public Item(String name, int price, int stock) {
this.name = name;
this.price = price;
this.stock = stock;
}
public String getName() { return name; }
public int getPrice() { return price; }
public int getStock() { return stock; }
public boolean isAvailable() { return stock > 0; }
public void decreaseStock() { if (stock > 0) stock--; }
@Override
public String toString() {
return name + " (" + price + "円) - 在庫: " + (stock > 0 ? stock + "個" : "売り切れ");
}
}
VendingMachine.java(自動販売機本体クラス)
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class VendingMachine {
private List<Item> items;
private int currentAmount;
private int sales;
public VendingMachine() {
items = new ArrayList<>();
currentAmount = 0;
sales = 0;
initializeItems();
}
private void initializeItems() {
items.add(new Item("お茶", 120, 5));
items.add(new Item("コーヒー", 130, 3));
items.add(new Item("コーラ", 150, 0));
items.add(new Item("水", 100, 10));
}
public void insertMoney(int amount) {
if (amount == 10 || amount == 50 || amount == 100 || amount == 500 || amount == 1000) {
currentAmount += amount;
System.out.println(amount + "円投入されました。現在の投入金額: " + currentAmount + "円");
} else {
System.out.println(amount + "円は投入できません。10円、50円、100円、500円、1,000円のみ利用可能です。");
}
}
public int getCurrentAmount() { return currentAmount; }
public void displayItems() {
System.out.println("--- 商品リスト ---");
if (items.isEmpty()) { System.out.println("現在、販売中の商品はありません。"); return; }
for (int i = 0; i < items.size(); i++) {
System.out.println((i + 1) + ". " + items.get(i).toString());
}
System.out.println("------------------");
}
public void purchaseItem(int itemIndex) {
int actualIndex = itemIndex - 1;
if (actualIndex < 0 || actualIndex >= items.size()) { System.out.println("無効な商品番号です。"); return; }
Item selectedItem = items.get(actualIndex);
if (!selectedItem.isAvailable()) { System.out.println("申し訳ありません。" + selectedItem.getName() + "は売り切れです。"); return; }
if (currentAmount < selectedItem.getPrice()) { System.out.println("投入金額が不足しています。" + selectedItem.getName() + "の価格は" + selectedItem.getPrice() + "円です。"); return; }
currentAmount -= selectedItem.getPrice();
selectedItem.decreaseStock();
sales += selectedItem.getPrice();
System.out.println(selectedItem.getName() + "を購入しました。");
returnChange();
}
public void returnChange() {
if (currentAmount > 0) { System.out.println("お釣り " + currentAmount + "円をお返しします。"); currentAmount = 0; }
else { System.out.println("お釣りはありません。"); }
System.out.println("ご利用ありがとうございました。");
}
public void run() {
Scanner scanner = new Scanner(System.in);
int choice = -1;
while (choice != 0) {
System.out.println("\n現在の投入金額: " + currentAmount + "円");
displayItems();
System.out.println("操作を選んでください: 1:お金投入 / 2:商品購入 / 3:終了");
System.out.print("番号を入力: ");
if (scanner.hasNextInt()) { choice = scanner.nextInt(); } else { System.out.println("無効な入力です。"); scanner.next(); continue; }
switch (choice) {
case 1:
System.out.print("投入する金額を入力してください (10, 50, 100, 500, 1000): ");
if (scanner.hasNextInt()) { insertMoney(scanner.nextInt()); } else { System.out.println("無効な入力です。"); scanner.next(); }
break;
case 2:
if (currentAmount <= 0) { System.out.println("先にお金を投入してください。"); break; }
System.out.print("購入したい商品の番号を入力してください: ");
if (scanner.hasNextInt()) { purchaseItem(scanner.nextInt()); } else { System.out.println("無効な入力です。"); scanner.next(); }
break;
case 3:
returnChange(); choice = 0; break;
default:
System.out.println("無効な選択です。1から3の番号を入力してください。");
}
}
scanner.close();
System.out.println("自動販売機プログラムを終了します。総売上: " + sales + "円");
}
}
Main.java(実行クラス)
public class Main {
public static void main(String[] args) {
VendingMachine myVendingMachine = new VendingMachine();
myVendingMachine.run();
}
}
まとめ|Javaの自動販売機作成でOOPの基礎を体得しよう
Javaで自動販売機を作る学習は、オブジェクト指向の基礎を一気に体得できる最短ルートです。
状態管理・入出力処理・入力チェックなど、実務で使うスキルが1つの題材に凝縮されているからです。
実際にクラス設計とコンソール操作を手を動かして体験できる点が強みです。
抽象概念を「動くコード」として確認できるため、初心者から中級者へステップアップする教材として最適です。
【要点まとめ】
- 自動販売機はオブジェクト指向の理解に最適な題材である
- クラス分割で現実の「モノ」をプログラムで再現できる
- 状態管理の考え方を変数とロジックで学べる
- 条件分岐や繰り返し処理の応用力が鍛えられる
- Javaの入出力処理(ScannerやSystem.out)に慣れる
- エラー処理やユーザー入力への対応力が身につく
- 完成したプログラムで動作確認・デバッグ力が向上する
- オブジェクト同士の連携や責務分担の理解が深まる
この記事の3クラス設計と発展課題までやり切れば、コード練習が「自分でクラス設計できる力」へと変わります。
次は別ドメイン(コインロッカーや貸出管理システムなど)で同じ3クラス構成を自作してみてください。
| REV. | DATE | 内容 |
|---|---|---|
| 6 | 2026-10-16 | 図解を描き直し(図面のデザインにそろえた) |
| 5 | 2026-10-14 | 一人称と言い回しの表記をそろえた |
| 4 | 2026-10-04 | 記載内容の事実確認と修正 |
| 3 | 2026-07-30 | 2026年7月時点の情報で全体を見直し(古い記述を3件更新) |
| … ほか1回の改訂 | ||
| 1 | 2025-04-20 | 初版 |




