「Javaで自動販売機を作ってみたいけど、どのクラスをどう分ければいいかわからない」——そんなJava初心者向けに、Item(商品)/VendingMachine(自販機本体)/Main(実行)の3クラス設計で動く自動販売機プログラムを4ステップで作る方法を解説します。コピペで30分で完成し、オブジェクト指向のカプセル化・クラス分割の意義まで実践的に身につきます。
この記事を読めば:
- 3クラス設計の役割分担(Item/VendingMachine/Main)が腹落ちする
- コピペで30分でお金投入・商品選択・お釣り計算が動くプログラムが完成する
- オブジェクト指向の「カプセル化」「責務分担」「クラス設計の判断基準」を実例で理解できる
私自身もJavaを学び始めた頃に自動販売機プログラムを作成し、オブジェクト指向への理解が一気に深まりました。自動販売機はクラス、オブジェクト、カプセル化など基本概念を一通り練習できる最適な題材です。Javaの基本文法(変数、条件分岐、繰り返しなど)を学んだ方が次のステップとして取り組むのにぴったりの内容です。
※本記事のコードはJava 11以降で動作します。2026年4月時点の最新LTSはJava 25(2025年9月16日リリース)で、premier supportは2030年9月まで提供されます。Java 26(2026年3月リリース)は非LTSのため、新規プロジェクトでは安定LTSのJava 21またはJava 25の使用を推奨します。
Javaで作る自動販売機の完成イメージ|3クラスで何が動くか

この記事で作成を目指すのは、コンソール上で動作するシンプルなテキストベースの自動販売機です。
具体的には、以下のような機能を持つものを想定しています。
自動販売機の処理フロー|起動から終了までの5ステップ
実装前に、プログラムの処理の流れを確認しておきましょう。全体像を把握しておくと、各ステップのコードの意味が理解しやすくなります。
- 起動 → 商品リストを初期化し、メニューを表示する
- 操作選択 → ①お金を投入 / ②商品を購入 / ③終了(お釣り返却) のいずれかを選ぶ
- お金の投入 → 受け付け可能な金種(10/50/100/500/1000円)かチェックし、投入金額に加算
- 商品の購入 → 商品番号を入力 → 在庫チェック → 金額チェック → 購入処理・在庫減算・お釣り返却
- 終了 → 残金があればお釣りとして返却し、総売上を表示してプログラム終了
このフローを「商品(Item)」「自動販売機本体(VendingMachine)」「実行クラス(Main)」の3つのクラスで実装していきます。
Javaで自動販売機を作る4ステップ|3クラス設計で実装する
それでは、実際にJavaプログラミングで自動販売機を作成していく手順を、4つのステップに分けて解説します。
自動販売機のクラス設計|Item・VendingMachine・Mainの役割
プログラムを作る前に、自動販売機を構成する要素を洗い出し、それらをどのようにクラスとして表現するかを設計します。
オブジェクト指向プログラミングでは、現実世界の「モノ」や「概念」を「クラス」としてモデル化します。今回は以下の3つのクラスに分けて実装します。
以下のように、まず「何が必要か」を考え、それぞれの役割と情報(属性)、そしてどのような操作(振る舞い)ができるかを整理するのがクラス設計の第一歩です。
商品 (Item)
- 属性(データ): 商品名 (name), 価格 (price), 在庫数 (stock)
- 振る舞い(メソッド): 商品情報を取得する、在庫を減らす など
お金 (Money)
今回はシンプルに、投入された合計金額を管理する変数で表現します。より詳細にするなら、硬貨や紙幣の種類と枚数を管理するクラスも考えられます。
自動販売機本体 (VendingMachine)
- 属性(データ): 商品リスト (itemList), 投入金額 (currentAmount), 売り上げ (sales) など
- 振る舞い(メソッド): お金を受け取る (insertMoney), 商品を表示する (displayItems), 商品を購入する (purchaseItem), お釣りを計算する (calculateChange) など
なぜ3つのクラスに分けるのか|OOPでの責務分担
クラスを分ける本当の価値は、「仕様の明確化」と「機能の部品化」の2点にあります。1つのクラスに全ロジックを書くと、商品の在庫管理を直したいだけなのに購入処理や入力チェックまで読まないと安全に変更できません。
本記事の3クラス構成は、オブジェクト指向の単一責任の原則(Single Responsibility Principle)を素直にあてはめたものです。
- Item: 「商品の状態(名前・価格・在庫)」だけを持つ
- VendingMachine: 「販売の振る舞い(投入・選択・お釣り・売上)」を担当
- Main: 「実行の起点(プログラムを起動してVendingMachineに任せる)」だけを担当
競合教材ではItems・Deposit・VendingMachine・Mainの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 型の変数で管理することにします。
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 + "円");
}
}Mainクラスで動作テスト|main()メソッドから実行する
最後に、作成したVendingMachineクラスを実際に動かしてみるための実行用クラスを作成し、動作を確認します。
Main (または VendingMachineTest など分かりやすい名前) という名前で新しいクラスファイルを作成し、main メソッドを記述します。
main メソッドは、Javaプログラムの実行開始点となる特別なメソッドです。
public class Main {
public static void main(String[] args) {
// VendingMachineクラスのインスタンス(実体)を作成
VendingMachine myVendingMachine = new VendingMachine();
// 自動販売機を実行
myVendingMachine.run();
}
}想定される出力例|コンソールに何が出れば成功か
うまく動けば、コンソールには以下のような出力が表示されます。動作確認の目安にしてください。
現在の投入金額: 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を入力して終了)
自動販売機プログラムを終了します。総売上: 120円
実行とデバッグ:
- 表示される指示に従って、お金を投入したり、商品を選択したりしてみてください。
- 意図した通りに動作するか確認します。
- お金は正しく加算されるか?
- 商品は正しく表示されるか?
- 購入処理は正しく行われるか?(金額、在庫)
- お釣りは正しいか?
- 在庫切れの商品を買おうとしたらどうなるか?
- お金が足りない場合はどうなるか?
- 不正な金額や商品番号を入力したらどうなるか?
※本記事の動作確認スクリーンショットは2025年4月にmacOS+OpenJDK 21で取得したものです。Java 21〜25いずれでも同様の出力が得られます。

これでJavaプログラミングによる基本的な自動販売機が完成しました。
Scannerクラスの詳しい使い方はこちらも参考にしてください。
Javaで自動販売機を作るときによくある質問【FAQ】
Q1. なぜItemクラスとVendingMachineクラスに分けるの?1ファイルでもいい?
1ファイルでも動きますが、商品の追加・在庫管理ロジックが自販機本体の処理と混ざって保守性が落ちます。Itemに「商品の状態」、VendingMachineに「販売の振る舞い」と責務を分けておくと、後でCSV読み込みやGUI化に拡張するときに変更箇所が局所化されます。Java Silverなどの試験でも頻出の「単一責任の原則(SRP)」を体感する練習になります。
Q2. お金専用クラス(Money)は作らなくていい?
初学者向けの教材としては不要です。投入金額はint型変数で管理すれば十分動き、本記事もその構成にしています。硬貨の枚数管理・釣銭切れの検出など仕様を増やす段階で初めてMoneyクラスを切り出すと、クラス分割の判断基準(変更理由が違うか)を体感できます。
Q3. Scannerを閉じ忘れるとどうなる?
標準入力ストリームのリソースリークが発生し、長時間動かすプログラムだと警告が出ます。本記事のコードではrun()メソッドの末尾でscanner.close()を呼び出しています。Java 7以降ではtry (Scanner scanner = new Scanner(System.in)) { ... }のtry-with-resources構文を使うと、ブロックを抜けたタイミングで自動クローズされるためより安全です。
Q4. 在庫切れの商品を選んだときの挙動は?
purchaseItem()メソッド内でselectedItem.isAvailable()を呼び、在庫0なら「申し訳ありません。○○は売り切れです。」とメッセージを出して購入処理を中断します。投入金額はそのまま残るので、別の商品を選び直すか終了でお釣りとして返却されます。
Q5. このコードはJavaのどのバージョンから動く?
Java 11以降で動作確認済みです。Listのダイヤモンド演算子・拡張for文など使っているのは古い文法のみのため、Java 8でも問題なく動きます。新規プロジェクトで学習する場合は2026年4月時点の最新LTSであるJava 25(2025年9月16日リリース、premier support 2030年9月まで)または安定LTSのJava 21の使用を推奨します。
発展課題|CSV読込・例外処理・GUI化で実力を伸ばす
基本的な自動販売機が完成したら、次のステップとして以下の発展課題に挑戦してみましょう。
- CSVから商品データを読み込む:
initializeItems()でハードコードしている商品を、CSVファイルから読み込む形式にするとファイルI/Oの練習になります。 - 硬貨の種類別管理クラスを追加する: 投入金額を合計値で管理するだけでなく、10円玉・50円玉・100円玉それぞれの枚数を管理する
Moneyクラスを作ると、より本格的なオブジェクト設計の練習になります。 - 例外処理を本格実装する: 無効な商品番号や負の金額投入に対して
IllegalArgumentExceptionなどをスローする実装にすることで、例外処理の理解が深まります。 - HashMapを使った実装に書き換える: 本記事では
ArrayList<Item>で商品を管理しましたが、HashMap<String, Integer>で商品名と在庫だけ管理する簡易版もあります。上級者向けQiita記事などで見かける実装です。 - GUIアプリ化する(JavaFX / Swing): コンソールベースからGUIベースに移行するとイベント駆動プログラミングを学べます。
完成コード一覧(3ファイル構成)
このプログラムは以下の3ファイル構成です。それぞれのコードをそのまま使えます。同じディレクトリに配置して javac *.java でコンパイルし、java Main で実行してください。Java 11以降で動作確認済み(2026年4月時点でJava 21・25 LTSでも同コードが動作することを確認)。
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クラス構成を自作してみてください。

