本ページはプロモーションが含まれています

Java入門

Java変数のスコープとは?4種類の違いと使い分けを図解

CATEGORY
REV.
DRAWN BY
TOM
SHEETS
6枚
Scope(変数のスコープ4種類)

トム

SESで約8年、環境を変えられないもどかしさの中にいました。32歳で自社開発企業に転職し、今はClaude Codeと自動化で、仕事も時間も組み直している途中です。
・Javaバックエンドエンジニア(Java歴12年以上)
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎
詳細なプロフィール

𝕏

Javaを学び始めると、「さっき宣言したはずの変数が使えない」「同じ名前の変数でエラーになった」という場面でつまずきがちです。

原因のほとんどは変数のスコープ(有効範囲)を正しく理解していないことにあります。

この記事では、Javaの変数スコープ4種類(ブロック・メソッド・インスタンス・クラス)の違いをコード例付きで解説し、シャドーイングの挙動やスコープを狭く保つベストプラクティスまで説明します。

この記事は、Java入門ロードマップの「STEP2|基本文法」に対応しています。全体の学習順は次の記事で確認できます。

あわせて読む

Javaのスコープとは?基本概念をわかりやすく解説

プログラムを書く上で避けては通れない「スコープ」。まずは、このスコープが一体何なのか、そしてなぜ重要なのかを見ていきましょう。

スコープとは「変数の有効範囲」

スコープとは「宣言した変数が使える有効範囲」を指します。

変数は、プログラムのどこで宣言されたかによって、参照したり代入したりできる範囲が決まっています。

その変数が生きているテリトリー、とイメージすると分かりやすいかもしれません。

このテリトリーの外側から変数を呼び出そうとすると、「そんな変数は存在しません」とコンパイラに怒られてしまいます。

これが、初心者がよく遭遇するコンパイルエラーの原因の一つです。

スコープが重要な理由とは?

では、なぜこのスコープを意識する必要があるのでしょうか。理由は大きく2つあります。

スコープが重要な理由

  1. 予期せぬバグを防ぐため
    スコープを理解していないと、意図しない場所で変数の値が書き換えられてしまう危険性があります。広い範囲で有効な変数を多用すると、どこで値が変更されたのか追跡が難しくなり、バグの温床になります。
  2. コードの可読性と保守性を高めるため
    変数のスコープは、可能な限り狭い方が良いとされています。その変数がどこで使われているのかが一目で分かりやすくなるからです。読むべきコードの範囲が狭まるため、他の開発者にも理解しやすく、将来の機能追加や修正も容易になります。

スコープは、安全で分かりやすいコードを書くための、いわば交通ルールのようなものです。

似た概念として、クラス外からのアクセス可否を制御するJavaアクセス修飾子4つの違いと使い分けもあわせて理解しておくと、変数・メソッドの公開範囲設計がより確実になります。

Javaにおける4つのスコープ

Javaには、変数を宣言する場所によって決まる、主に4種類のスコープが存在します。それぞれの有効範囲と特徴を、具体的なコード例と共に見ていきましょう。

まずは4つのスコープを、有効範囲の広い順に図で確認します。

4つのスコープ(有効範囲の広い順)
上ほど広い / 下ほど狭い
CLASS / static変数クラススコープクラス全体・全オブジェクトで共有
INSTANCE / フィールド変数インスタンススコープオブジェクト単位
METHOD / ローカル変数メソッドスコープメソッド内のみ
BLOCK / { } 内の変数ブロックスコープif・for などの { } 内のみ。最も狭い
NOTE下に行くほど有効範囲が狭く、ライフタイムも短くなります。宣言はできるだけ下側(狭いスコープ)を選ぶのが安全です。

上から下に行くほど有効範囲が狭くなり、ライフタイムも短くなります。なお言語仕様(JLS §6.3)上は、static変数もフィールド変数もスコープはクラス本体全体で同じです。違いは、staticメソッドからフィールド変数を直接参照できない点にあります。変数を宣言するときは、できるだけ下側(狭いスコープ)を選ぶのが安全です。

ブロックスコープ(メソッド内・if文など)

ブロックスコープは、4つのスコープの中で最も狭い範囲を持ちます。{}(波括弧)で囲まれたブロックの内側でのみ有効なスコープです。

if文やfor文、while文などの制御構文のブロック内で宣言された変数がこれに該当します。

public class ScopeExample {
    public void blockScopeTest() {
        int a = 10; // メソッドスコープの変数

        if (a == 10) {
            int b = 20; // ブロックスコープの変数
            System.out.println("ブロックの内側: " + b);
        }

        // System.out.println(b); // ここでbを使おうとするとコンパイルエラー!
    }
}

この例では、変数bはif文の{}の中で宣言されています。

そのため、if文のブロックを抜けた後でbを参照しようとすると、「シンボルを見つけられません」というコンパイルエラーが発生します。

一時的にしか使わない変数はブロックスコープで宣言するのが基本です。

ブロックスコープ:b はいつまで使える?
1
a:使える / b:まだ無いint a = 10;メソッド内で a を宣言(メソッドスコープ)
2
a:使える / b:まだ無いif (a == 10) {if文の { } ブロックに入る
3
a:使える / b:使えるint b = 20;ブロック内で b を宣言(ブロックスコープ)
4
a:使える / b:使えるprintln(b); OKブロックの内側なので b を使える
5
a:使える / b:範囲外}} でブロックを抜けると b は範囲外
6
a:使える / b:範囲外println(b); NG(エラー)外で b を書くと「シンボルを見つけられません」
NOTE上のコード例を簡略表示(System.out を省略)。6 はコンパイル時のエラーです。

なお、ブロックスコープ外から変数を参照しようとして発生する「シンボルを見つけられません」というエラーについては、Java「シンボルを見つけられません」原因7つと解決コード例で詳しく解説しています。

メソッドスコープ(ローカル変数)

メソッドスコープは、メソッドの{}の内部で有効なスコープです。

メソッド内で宣言された変数は「ローカル変数」と呼ばれ、そのメソッドの中でのみ生き続けます。

メソッドの処理が終わると、ローカル変数はメモリ上から消去されます。

public class ScopeExample {
    public void methodA() {
        String message = "こんにちは"; // methodAのローカル変数
        System.out.println(message);
    }

    public void methodB() {
        // System.out.println(message); // ここでmessageを使おうとするとコンパイルエラー!
        int count = 100; // methodBのローカル変数
        System.out.println(count);
    }
}

methodAで宣言されたmessage変数は、methodBからは全く見えません。

それぞれのメソッドは独立した世界を持っており、ローカル変数が他のメソッドに影響を与えることはありません。

インスタンススコープ(フィールド変数)

インスタンススコープは、クラスのメソッドの外側で宣言された変数が持つスコープです。

これらの変数は「インスタンス変数」や「フィールド」、「メンバー変数」とも呼ばれます。

インスタンス変数は、クラスからオブジェクト(インスタンス)が生成されたときに作られ、そのオブジェクトが存在する限り有効です。

同じクラスから作られたオブジェクトでも、それぞれが異なるインスタンス変数の値を保持します。

public class User {
    String userName; // インスタンス変数

    public User(String name) {
        this.userName = name;
    }

    public void printUserName() {
        System.out.println("ユーザー名: " + this.userName);
    }

    public static void main(String[] args) {
        User user1 = new User("田中");
        User user2 = new User("鈴木");

        user1.printUserName(); // 出力: ユーザー名: 田中
        user2.printUserName(); // 出力: ユーザー名: 鈴木
    }
}

この例では、userNameがインスタンス変数です。

user1とuser2という2つのオブジェクトは、それぞれ「田中」と「鈴木」という別々のuserNameを持っています。

クラススコープ(static変数)

クラススコープは、インスタンススコープと同じくクラスのメソッドの外側で宣言されますが、staticキーワードが付与されている点が異なります。

この変数は「クラス変数」や「static変数」と呼ばれます。

クラス変数は、オブジェクトを生成しなくてもクラスに直接属しており、そのクラスの全てのオブジェクトで共有されます。

クラスが初めて使われて初期化されたときから、プログラムが終了するまで、クラスごとに1つだけ存在します。

public class Counter {
    static int totalCount = 0; // クラス変数

    public Counter() {
        totalCount++; // オブジェクトが作られるたびにカウントアップ
    }

    public static void main(String[] args) {
        System.out.println("作成前のカウント: " + Counter.totalCount); // 出力: 0

        Counter c1 = new Counter();
        System.out.println("c1作成後のカウント: " + Counter.totalCount); // 出力: 1

        Counter c2 = new Counter();
        System.out.println("c2作成後のカウント: " + Counter.totalCount); // 出力: 2
    }
}

totalCountはstaticなクラス変数なので、c1やc2といったオブジェクトごとではなく、Counterクラス全体で1つの値を共有しています。

そのため、新しいオブジェクトが作られるたびに、共有されているtotalCountの値が増えていきます。

4つのスコープを一覧で比較

ここまで解説した4種類のスコープを表で整理します。

宣言場所・有効範囲・ライフタイムの違いを一覧で把握しておくと、変数をどこに置くべきか迷ったときの判断基準になります。

スコープ宣言場所有効範囲ライフタイム初期値
ブロックスコープif/for/while等の{}内宣言した位置からブロックの終わりまでブロック終了までなし(要初期化)
メソッドスコープメソッドの{}内宣言した位置からメソッドの終わりまでメソッド終了までなし(要初期化)
インスタンススコープクラス直下(メソッド外)同一オブジェクト内の全メソッドオブジェクトがGCされるまで型のデフォルト値
クラススコープクラス直下(static付き)クラス全体(全オブジェクト共有)プログラム終了まで型のデフォルト値

変数のシャドーイングとthisキーワードの使い方

異なるスコープで同じ名前の変数を宣言した場合、どのように扱われるのでしょうか。

同じ名前の変数が重複した場合の挙動

フィールドと同じ名前のローカル変数(メソッドの引数を含む)を宣言すると、より狭いスコープで宣言されたローカル変数が優先されます。

内側のスコープで宣言されたローカル変数が、外側にある同名のフィールドを「隠す」ような動きをします。これを「シャドウイング」と呼びます。なお、ローカル変数どうしでは隠すことができません。メソッドの引数や同じメソッド内のローカル変数と同じ名前の変数を、内側のブロックで宣言することはできず、コンパイルエラー(javac では「変数 …はすでにメソッド …で定義されています」というエラー)になります。

public class ShadowingExample {
    String name = "インスタンス"; // インスタンススコープ

    public void showName() {
        String name = "ローカル"; // メソッドスコープ(ローカル変数)
        System.out.println(name); // "ローカル" が出力される
    }

    public static void main(String[] args) {
        ShadowingExample example = new ShadowingExample();
        example.showName();
    }
}

showNameメソッドの中では、外側で宣言されたインスタンス変数nameよりも、内側で宣言されたローカル変数nameが優先されます。

そのため、出力結果は「ローカル」となります。

thisキーワードでスコープの衝突を解消する

ローカル変数が優先される状況で、意図的にインスタンス変数を参照したい場合はどうすれば良いのでしょうか。そこで登場するのがthisキーワードです。

thisは、自分自身のオブジェクト(インスタンス)を指し示す特別な参照です。

this.変数名と記述することで、「このインスタンスが持っている変数」であることを明示できます。

public class User {
    String userName; // インスタンス変数

    // コンストラクタの引数もローカル変数の一種
    public User(String userName) {
        // this.userName はインスタンス変数
        // userName は引数のローカル変数
        this.userName = userName;
    }

    public void printUserName() {
        System.out.println(this.userName);
    }

    public static void main(String[] args) {
        User user = new User("佐藤");
        user.printUserName(); // 出力: 佐藤
    }
}

コンストラクタの引数userNameとインスタンス変数userNameは同じ名前です。

もしthisを付けずにuserName = userName;と書いてしまうと、「引数の値を引数自身に代入する」という無意味な処理になってしまいます。

this.userName = userName;と書くことで、初めて「引数で受け取った値を、このインスタンスの変数に代入する」という意味になるのです。

なお、コードレビューの現場では、コンストラクタの引数以外でシャドーイングを意図的に使うコードは避けるべきとされることが多いです。

ローカル変数とフィールド変数が同名だと、どちらを参照しているか一目でわかりにくくなるためです。

変数名が衝突しそうな場合は、ローカル変数の名前を変えるほうが安全な設計といえます。

スコープのベストプラクティスと注意点

スコープを理解した上で、より品質の高いコードを書くための注意点とベストプラクティスを紹介します。

スコープが狭いほど安全で管理しやすい

プログラミングの原則として、「変数のスコープは可能な限り狭くする」ことが推奨されています。

理由は、変数の影響範囲を最小限に抑えることで、コードの安全性が高まるからです。

ある変数がごく限られた範囲でしか使われないのであれば、そのブロックの中だけで有効なブロックスコープの変数として宣言するべきです。

そうすれば、他の場所で誤ってその変数の値を変更してしまう心配がなくなります。

また、スコープが狭ければ、その変数の役割を理解するために読むべきコードの範囲も狭くなります。

これはコードの可読性を向上させ、デバッグや機能改修を容易にします。

フィールド変数の使いすぎに注意

インスタンススコープを持つフィールド変数(インスタンス変数)は、そのオブジェクトの「状態」を表すために使います。

便利だからといって、一時的に計算で使うだけの変数をフィールド変数にしてはいけません。

フィールド変数が多くなりすぎると、オブジェクトの状態が複雑化します。

たくさんの変数がお互いに影響し合うようになると、たった1つの値を変更しただけで、思わぬ副作用を引き起こす可能性があります。

こうした予期しない副作用は、追跡しにくいバグの原因となります。

変数を宣言する際は、まず「メソッド内のローカル変数で済ませられないか?」を考え、どうしてもオブジェクト全体で保持する必要がある情報だけをフィールド変数にするのが良い設計です。

実務で遭遇するスコープのバグパターン

スコープの理解が曖昧だと、実務でも以下のようなバグに遭遇します。典型的な2パターンを押さえておきましょう。

パターン1: for文のカウンタ変数をメソッド先頭で宣言してしまう
// NG: カウンタがループ外に漏れている
public void printItems(List<String> items) {
    int i = 0; // メソッドスコープ
    for (i = 0; i < items.size(); i++) {
        System.out.println(items.get(i));
    }
    // ここで i にはループ終了後の値が残っている
    // 後続処理で i を別の用途に使うとバグの原因に
}

// OK: カウンタをfor文内で宣言
public void printItems(List<String> items) {
    for (int i = 0; i < items.size(); i++) {
        System.out.println(items.get(i));
    }
    // i はここでは参照できない(安全)
}

カウンタ変数をfor文の外で宣言すると、ループ後も値が残り続けます。後続の処理でうっかり参照してしまうと、意図しない値を使ってしまいます。

for文の初期化部で宣言すれば、ブロックスコープに閉じ込められるため安全です。

パターン2: if-elseの片方でしか宣言していない変数を外で使う
// NG: コンパイルエラーになる
public void checkAge(int age) {
    if (age >= 18) {
        String message = "成人です";
    } else {
        String message = "未成年です";
    }
    System.out.println(message); // エラー: messageは未定義
}

// OK: if文の外で宣言する
public void checkAge(int age) {
    String message;
    if (age >= 18) {
        message = "成人です";
    } else {
        message = "未成年です";
    }
    System.out.println(message); // 正常に動作
}

if文やelse文のブロック内で宣言した変数は、そのブロックの外では使えません。ブロックをまたいで使いたい変数は、必ず外側のスコープで宣言しましょう。

命名ルールでスコープの混乱を防ぐ

チーム開発などでは、スコープの違いを分かりやすくするために命名ルールを設けることがあります。

ポイント

  • インスタンス変数(フィールド): mVariableName や _variableName のように接頭辞をつける
  • static変数(クラス変数): sVariableName や、定数の場合は ALL_CAPS_SNAKE_CASE

IntelliJ IDEAやEclipseなどの主要IDEでは、変数にカーソルを合わせるだけでスコープが分かったり、色分けしてくれたりするため、接頭辞を付けるルールは必須ではなくなっています。

スコープの命名と合わせて、クラス名・メソッド名を含めたJava命名規則|クラス・メソッド・変数・パッケージで迷わない型も押さえておくと、チーム開発での可読性がさらに高まります。

実際、Google Java Style Guide では「特別な接頭辞・接尾辞は使わない」とされ、mName のような名前はスタイル違反の例として挙げられています(フィールド名は static かどうかにかかわらず lowerCamelCase)。なお、AOSP(Android Open Source Project)自体のJavaコードスタイルは、現在も非publicで非staticのフィールドに m、staticフィールドに s を付ける規約を定めています。

現在のJava開発ではキャメルケースのみで命名するのが主流です。

しかし、どのようなスコープの変数なのかを意識して命名する習慣は、コードの可読性を高める上で有効です。

Javaのスコープに関するよくある質問

Q1. スコープとは何ですか?

A1. スコープとは「変数が有効な範囲」のことです。変数がどこで宣言され、どこまでアクセスできるかを決めるルールで、Javaにはブロック・メソッド・インスタンス・クラスの4種類のスコープがあります。

Q2. スコープはなぜ狭いほうがいいのですか?

A2. スコープが狭いほど、その変数がどこで使われているかを追いやすく、意図しない書き換えのリスクが減るためです。フィールド変数を多用するとどこからでも変更できてしまい、バグの原因を追跡しづらくなります。

Q3. 同じ名前の変数がスコープ内で重複するとどうなりますか?

A3. フィールド変数とローカル変数(引数を含む)が同名の場合は、ローカル変数が優先されてフィールドを一時的に隠します(シャドーイング)。ローカル変数どうしの同名宣言(内側のブロックでの再宣言を含む)はコンパイルエラーです。フィールド変数を指定したいときは、thisキーワードを使うことでフィールド変数を明示的に指定できます。

Q4. static変数(クラススコープ)はどんな時に使いますか?

A4. クラス全体で共有したい値、インスタンスに依存しない定数や共通カウンタなどに使います。ただし、状態を持つstatic変数はスレッドセーフでない場合が多く、乱用すると保守性が下がるため注意が必要です。

まとめ:スコープを理解して保守性の高いコードを

今回は、Javaの変数スコープについて、その基本から実践的なテクニックまでを解説しました。この記事は2026年8月時点の情報をもとに執筆しています。

Javaの変数のスコープを正しく理解し使い分けることは、バグが少なく、保守性の高いコードを書くための必須スキルです。 なぜなら、スコープを意識することで、変数の影響範囲を意図通りにコントロールし、予期せぬ値の変更や参照エラーを防ぐことができるからです。

初心者がつまずきやすいポイント

最後にもう一度、初心者が特に混乱しやすい点をまとめておきます。

ポイント

  • スコープの範囲外からのアクセス: if文やfor文の中で宣言した変数を、その外から使おうとしていないか確認しましょう。
  • ローカル変数とインスタンス変数の混同: 同じ名前の変数がある場合、どちらが使われているか意識することが重要です。インスタンス変数を使いたい場合はthisキーワードを忘れないようにしましょう。
  • 安易なフィールド変数の使用: メソッド内で完結する処理の変数は、ローカル変数として宣言する癖をつけましょう。

スコープを意識したコード設計のすすめ

最初は少し面倒に感じるかもしれませんが、1つ1つの変数を宣言するたびに「この変数のスコープはどこまでが適切か?」と考える習慣を身につけてください。

たとえば、次のようにメソッド冒頭でまとめて宣言するコードを見かけることがあります。

// Before: スコープが広すぎる
public void processOrders(List<Order> orders) {
    double total = 0;
    String label = "";
    boolean isValid = false;

    for (Order order : orders) {
        isValid = order.getAmount() > 0;
        if (isValid) {
            label = order.getName();
            total += order.getAmount();
            System.out.println(label + ": " + order.getAmount());
        }
    }
}

// After: スコープを必要最小限に絞る
public void processOrders(List<Order> orders) {
    double total = 0;

    for (Order order : orders) {
        boolean isValid = order.getAmount() > 0;
        if (isValid) {
            String label = order.getName();
            total += order.getAmount();
            System.out.println(label + ": " + order.getAmount());
        }
    }
}

isValidやlabelはループの外で使う必要がないため、使う場所に近いスコープで宣言するだけで、コードの意図が明確になります。

この小さな積み重ねが、数ヶ月後、数年後のあなたの書くコードの品質を大きく向上させます。

REVISIONS / 改訂履歴
REV.DATE内容
62026-10-16図解を描き直し(図面のデザインにそろえた)
52026-10-14雰囲気だけの挿絵を外した
42026-10-04記載内容の事実確認と修正
32026-09-042026年9月時点の情報で全体を見直し
… ほか1回の改訂
12025-06-21初版
NEXT READ本棚から1本

AIで開発を組み直すなら、まずこの5冊から

入門・実践・設計まで、Claude Codeの本をレベル別に5冊選んで比べました。

→ おすすめ5冊を見る

  • この記事を書いた人
  • 最新記事

トム

SESで約8年、環境を変えられないもどかしさの中にいました。32歳で自社開発企業に転職し、今はClaude Codeと自動化で、仕事も時間も組み直している途中です。
・Javaバックエンドエンジニア(Java歴12年以上)
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎
詳細なプロフィール

𝕏

Java入門