012-001-003

static変数:来店者カウンターシステム

中級

問題説明

1. 問題の背景と目的

この問題では、複数の店舗で共有される来店者数を管理するシステムを作成します。実務では、チェーン店の本部が全店舗の総来店者数を把握したい場合など、複数の場所で1つの値を共有する必要がある場面が多くあります。このような場合に、static変数を使うことで、プログラム全体で1つの値を共有し、どこからでもアクセス・更新できるようになります。

この学習は、次のステップである「staticメソッド」や「クラス設計」の理解につながります。static変数の概念を理解することで、オブジェクト指向プログラミングにおける「クラスレベルのデータ」と「インスタンスレベルのデータ」の違いを学ぶ基礎が身につきます。

2. 前提知識の詳細説明

static変数とは何か

static変数は、クラス全体で1つだけ存在する変数です。通常の変数(インスタンス変数)は、オブジェクトを作成するたびに新しく作られますが、static変数はクラスがメモリに読み込まれた時点で1つだけ作られ、プログラムが終了するまで存在し続けます。

例えば、銀行口座のクラスを考えてみましょう。各口座には個別の残高(インスタンス変数)がありますが、全口座の総預金額(static変数)は銀行全体で1つだけ存在します。

なぜstatic変数が必要なのか

  1. データの共有: すべてのメソッドやインスタンスで同じ値を参照・更新できます
  2. メモリの効率化: 1つの変数を共有するため、メモリを節約できます
  3. 一貫性の保証: 複数の場所で同じ値を使うため、データの一貫性が保たれます

どう使うのか

static変数は、クラスの先頭(メソッドの外側)で宣言します:

static int totalVisitors = 0;
```java

この宣言により、「totalVisitors」という名前のint型の変数が、プログラム全体で1つだけ作られます。初期値は0です。メソッド内から「totalVisitors」という名前で直接アクセスできます。

### <a href="https://javadrill.tech/problems/002">Scanner</a> クラスによる入力

Scannerクラスは、キーボードからの入力を読み取るためのクラスです。使い方は以下の通りです:

1. **インポート**: プログラムの先頭で「import java.util.<a href="https://javadrill.tech/problems/002">Scanner</a>;」と書きます
2. **インスタンス作成**: 「<a href="https://javadrill.tech/problems/002">Scanner</a> sc = new <a href="https://javadrill.tech/problems/002">Scanner</a>(System.in);」でScannerオブジェクトを作成します
3. **整数の読み取り**: 「int n = sc.nextInt();」で整数を1つ読み取ります
4. **クローズ**: 使い終わったら「sc.close();」でリソースを解放します

### 累積加算(+=<a href="https://javadrill.tech/problems/003">演算子</a>)

「+=」<a href="https://javadrill.tech/problems/003">演算子</a>は、現在の値に新しい値を加算して、結果を元の変数に代入します。

例:
```java
totalVisitors += 100;  // totalVisitors = totalVisitors + 100 と同じ
```java

もしtotalVisitorsが300だった場合、この行の実行後はtotalVisitorsは400になります。

## 3. コードの行ごとの詳細解説

### クラス宣言とstatic変数の宣言

```java
public class VisitorCounter {
    static int totalVisitors = 0;
```java

**1行目**: 「VisitorCounter」という名前の公開クラスを宣言しています。このクラスが、来店者カウンターの機能を提供します。

**2行目**: static変数「totalVisitors」を宣言し、初期値0を設定しています。この変数は、プログラムが開始された時点でメモリ上に1つだけ作られ、すべてのメソッドから共有されます。この時点では、総来店者数は0人です。

### mainメソッドの開始とScannerの準備

```java
public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);
    int n = sc.nextInt();
```java

**3行目**: mainメソッドを宣言しています。プログラムはここから実行が始まります。

**4行目**: Scannerオブジェクトを作成し、変数scに代入しています。「System.in」は<a href="https://javadrill.tech/problems/002/001">標準入力</a>(キーボード)を表します。これにより、scを使ってキーボードからの入力を読み取れるようになります。

**5行目**: 最初の整数を読み取り、変数nに代入しています。この値は、これから何回来店者数を記録するかを表します。例えば、nが3なら、3回の入力を受け付けます。

### ループによる来店者数の記録

```java
for (int i = 0; i < n; i++) {
    int visitors = sc.nextInt();
```java

**6行目**: forループを開始しています。変数iを0から始めて、nより小さい間繰り返します。つまり、n回繰り返すことになります。例えば、n=3なら、i=0, 1, 2の3回実行されます。

**7行目**: 来店者数を読み取り、変数visitorsに代入しています。この値は、この回に記録する来店者の人数を表します。例えば、visitorsが100なら、100人の来店者を記録することになります。

### 入力検証(エラーハンドリング)

```java
if (visitors == 0) {
    System.out.println("Error: Visitor count must be at least 1");
    sc.close();
    return;
}
```java

**8-12行目**: ゼロチェックを行っています。来店者数が0の場合、エラーメッセージを表示してプログラムを終了します。来店者数は1人以上である必要があるため、0は不正な値です。「return;」でmainメソッドを終了し、プログラムが停止します。

```java
if (visitors < 0) {
    System.out.println("Error: Visitor count must be positive");
    sc.close();
    return;
}
```java

**13-17行目**: 負の値チェックを行っています。来店者数が負の数の場合、エラーメッセージを表示してプログラムを終了します。来店者数は必ず正の数(1以上)である必要があります。

```java
if (visitors > 1000) {
    System.out.println("Error: Visitor count exceeds maximum (1000)");
    sc.close();
    return;
}
```java

**18-22行目**: 上限チェックを行っています。来店者数が1000を超える場合、エラーメッセージを表示してプログラムを終了します。この問題では、1回の記録で最大1000人までと制限されています。

### static変数の更新と結果表示

```java
totalVisitors += visitors;

System.out.println("Store added: " + visitors + " visitors");
System.out.println("Total visitors: " + totalVisitors);
```java

**23行目**: static変数「totalVisitors」に、今回の来店者数「visitors」を加算しています。例えば、totalVisitorsが300でvisitorsが100なら、実行後totalVisitorsは400になります。この行が、static変数を更新している最も重要な部分です。

**25行目**: 今回追加された来店者数を表示しています。「+」演算子で文字列と数値を連結しています。例: 「Store added: 100 visitors」

**26行目**: 現在の総来店者数を表示しています。static変数の現在の値を出力しているため、累積された結果が表示されます。例: 「Total visitors: 400」

### 進捗バーの表示(視覚的フィードバック)

```java
System.out.print("[");
int barLength = totalVisitors / 10;
for (int j = 0; j < barLength; j++) {
    System.out.print("=");
}
System.out.println("]");
```java

**28行目**: 進捗バーの開始を示す「[」を表示します。改行せずに表示するため、「print」メソッドを使用しています。

**29行目**: 進捗バーの長さを計算しています。総来店者数を10で割った値が、表示する「=」記号の数になります。例えば、totalVisitorsが450なら、barLengthは45になります。

**30-32行目**: barLengthの数だけ「=」記号を繰り返し表示します。forループで0からbarLength-1まで繰り返し、各回で「=」を1つ表示します。例: 45回繰り返すと「=============================================」となります。

**33行目**: 進捗バーの終了を示す「]」を表示し、改行します。これで1行の進捗バー表示が完成します。例: 「[=============================================]」

### リソースの解放

```java
sc.close();
```java

**35行目**: Scannerオブジェクトをクローズしてリソースを解放しています。これは、使い終わったリソースを適切に片付けるための重要な処理です。

### メモリ状態の変化(例: 入力が100, 200の場合)

**初期状態**: totalVisitors = 0

**1回目の入力後** (visitors = 100):
- totalVisitors += 100 → totalVisitors = 100
- 表示: 「Store added: 100 visitors」「Total visitors: 100」「[==========]」

**2回目の入力後** (visitors = 200):
- totalVisitors += 200 → totalVisitors = 300
- 表示: 「Store added: 200 visitors」「Total visitors: 300」「[==============================]」

このように、static変数は前回の値を保持したまま、新しい値を累積していきます。

## 4. よくある間違いと修正例

### 間違い1: static修飾子を忘れる

**誤った例**:
```java
int totalVisitors = 0;  // staticがない!
```java

**理由**: <a href="https://javadrill.tech/problems/012">static</a>を付けないと、インスタンス変数になってしまいます。mainメソッドはstaticメソッドなので、<a href="https://javadrill.tech/problems/012">static</a>でない変数にはアクセスできません。コンパイルエラーが発生します。

**正しい書き方**:
```java
static int totalVisitors = 0;  // staticを付ける
```java

### 間違い2: 累積加算の代わりに代入を使う

**誤った例**:
```java
totalVisitors = visitors;  // これは代入!累積されない
```java

**理由**: この書き方では、前の値が上書きされてしまい、累積されません。例えば、最初に100を記録した後、200を記録すると、totalVisitorsは300ではなく200になってしまいます。

**正しい書き方**:
```java
totalVisitors += visitors;  // +=演算子で累積加算
```java

### 間違い3: Scannerのクローズを忘れる

**誤った例**:
```java
public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);
    // ... 処理 ...
    // sc.close()を書き忘れている!
}
```java

**理由**: Scannerを使い終わった後、クローズしないとリソースリーク(メモリの無駄遣い)が発生します。小さなプログラムでは問題になりませんが、大きなプログラムではメモリ不足の原因になります。

**正しい書き方**:
```java
public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);
    // ... 処理 ...
    sc.close();  // 必ずクローズする
}
```java

### 間違い4: 進捗バーで整数除算の結果を考慮しない

**誤った例**:
```java
int barLength = totalVisitors / 10.0;  // doubleで計算してintに代入
```java

**理由**: 10.0で割ると結果がdouble型になりますが、それをint型に代入すると小数部分が切り捨てられます。ただし、この場合は意図した通りに動作しますが、整数除算「/10」を使う方が明確です。

**正しい書き方**:
```java
int barLength = totalVisitors / 10;  // 整数除算で明確に
```java

### 間違い5: エラーチェックの順序が間違っている

**誤った例**:
```java
if (visitors > 1000) {
    // 上限チェック
}
if (visitors < 0) {
    // 負の値チェック(後でチェックしている)
}
```java

**理由**: エラーチェックは、より基本的な検証から順に行うべきです。負の値チェックは最も基本的な検証なので、先に行うべきです。

**正しい書き方**:
```java
if (visitors < 0) {
    // まず負の値チェック
}
if (visitors == 0) {
    // 次にゼロチェック
}
if (visitors > 1000) {
    // 最後に上限チェック
}
```java

## 5. 実践的なデバッグのヒント

### エラーが出た時の対処法

**1. コンパイルエラー: 「non-static variable cannot be referenced from a static context」**

このエラーは、staticメソッド(mainメソッド)から、staticでない変数にアクセスしようとした時に出ます。解決策は、変数にstatic修飾子を追加することです。

**2. 実行時エラー: 「InputMismatchException」**

このエラーは、nextInt()で整数を読み取ろうとした時に、入力が整数でなかった場合に出ます。入力が正しいか確認してください。

### println()を使ったデバッグ方法

プログラムが期待通りに動作しない場合、変数の値を途中で表示して確認します:

```java
System.out.println("Debug: visitors = " + visitors);
System.out.println("Debug: totalVisitors = " + totalVisitors);
```java

これにより、変数が期待通りの値になっているか確認できます。

### よく見るエラーメッセージの意味

- **「';' expected」**: セミコロン(;)が抜けています
- **「cannot find symbol」**: 変数名やメソッド名のスペルミスです
- **「incompatible types」**: データ型が合っていません(例: Stringをintに代入しようとしている)

## 6. 発展的な内容

この問題を理解できたら、次のステップに進みましょう:

### staticメソッドの学習

static変数と同様に、staticメソッドもクラス全体で共有されます。例えば、進捗バーを表示する処理を別のメソッドに分けることができます:

```java
static void displayProgressBar(int total) {
    System.out.print("[");
    int barLength = total / 10;
    for (int j = 0; j < barLength; j++) {
        System.out.print("=");
    }
    System.out.println("]");
}
```java

これをmainメソッドから「displayProgressBar(totalVisitors);」と呼び出すことで、コードが読みやすくなります。

### より実務的な応用例

実務では、static変数は以下のような場面で使われます:

1. **アプリケーション全体の設定値**: 例えば、最大接続数や制限時間などの定数
2. **シングルトンパターン**: アプリケーション全体で1つだけのインスタンスを管理する
3. **ログ出力**: すべてのクラスから共通のログファイルに出力する

### パフォーマンスの最適化

このプログラムでは、進捗バーを毎回ループで描画していますが、大きな数になるとループが多くなります。より効率的な方法として、String.repeat()メソッド(Java 11以降)を使うことができます:

```java
String bar = "=".repeat(totalVisitors / 10);
System.out.println("[" + bar + "]");
```java

この書き方は、より簡潔で読みやすくなります。

## 7. 関連する学習項目

次に学ぶべきトピックやこの問題と関連する概念:

- **staticメソッド**(カテゴリ012-002): static変数と組み合わせて、クラス全体で共有されるメソッドを作る
- **クラス設計の基礎**(カテゴリ007): staticとインスタンスメンバの使い分けを学ぶ
- **<a href="https://javadrill.tech/problems/004">配列</a>の基礎**(カテゴリ006): 複数の値を効率的に管理する方法
- **メソッドの分割**(カテゴリ005): コードを再利用可能な部品に分ける技術
- **<a href="https://javadrill.tech/problems/019/001">例外処理</a>**(カテゴリ019): エラーをより洗練された方法で処理する

テストケース例

※ 出力例はプログラミングの国際標準に準拠し英語で表示しています

入力:
3
100
200
150
期待される出力:
Store added: 100 visitors
Total visitors: 100
[==========]
Store added: 200 visitors
Total visitors: 300
[==============================]
Store added: 150 visitors
Total visitors: 450
[=============================================]
入力:
2
500
300
期待される出力:
Store added: 500 visitors
Total visitors: 500
[==================================================]
Store added: 300 visitors
Total visitors: 800
[================================================================================]
入力:
1
1000
期待される出力:
Store added: 1000 visitors
Total visitors: 1000
[====================================================================================================]
入力:
5
1
1
1
1
1
期待される出力:
Store added: 1 visitors
Total visitors: 1
[]
Store added: 1 visitors
Total visitors: 2
[]
Store added: 1 visitors
Total visitors: 3
[]
Store added: 1 visitors
Total visitors: 4
[]
Store added: 1 visitors
Total visitors: 5
[]
入力:
1
0
期待される出力:
Error: Visitor count must be at least 1
入力:
1
-50
期待される出力:
Error: Visitor count must be positive
入力:
1
1500
期待される出力:
Error: Visitor count exceeds maximum (1000)
❌ テストに失敗したケースがあります
❌ エラー発生

あなたの解答

現在のモード: 自分のコード
import java.util.Scanner;

public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
// ここにコードを書いてください

sc.close();
}
}
0 B / 5 MB

残り 8 回実行可能