こんにちは、かつコーチです。
「public static void mainのstaticって何のためについているんだろう」と疑問に思ったことはありませんか。
static(クラスに1つだけ存在する、インスタンスに依存しないメンバーを表すキーワード)は、Java学習の初期でつまずきやすいポイントの1つです。
この記事では、staticとインスタンスの違いを、具体例を通して整理します。
staticとは
クラスに属するかインスタンスに属するか
Javaのクラスには、大きく分けて2種類のメンバー(変数・メソッド)があります。
- インスタンスメンバー:
newでオブジェクト(インスタンス)を作るたびに、それぞれ別々に持つデータ・処理 - staticメンバー:クラス自体に1つだけ存在し、すべてのインスタンスで共有されるデータ・処理
public class Counter {
static int totalCount = 0; // クラスに1つだけ(staticメンバー)
int myCount = 0; // インスタンスごとに別々(インスタンスメンバー)
public void increment() {
totalCount++;
myCount++;
}
}
totalCountはCounterクラス全体で1つしか存在しませんが、myCountはnew Counter()するたびに新しく作られます。
なぜstaticが必要なのか
「インスタンスを作らなくても呼び出せる」「全インスタンスで値を共有したい」という場面で、staticが役立ちます。
たとえばmainメソッドがstaticなのは、プログラム起動時にはまだ1つもインスタンスが作られていないためです。
インスタンスがない状態でも呼び出せるように、mainはstaticである必要があります。
基本の書き方・実装手順
手順1:staticなし(インスタンスメンバー)の場合
public class Dog {
String name;
public Dog(String name) {
this.name = name;
}
public void bark() {
System.out.println(name + ":ワン!");
}
}
Dog dog1 = new Dog("ポチ");
Dog dog2 = new Dog("タロウ");
dog1.bark(); // ポチ:ワン!
dog2.bark(); // タロウ:ワン!
nameはインスタンスごとに独立しているため、dog1とdog2で別々の値を持てます。
手順2:staticあり(全インスタンス共有)の場合
public class Dog {
static int dogCount = 0; // 全インスタンスで共有するカウンター
String name;
public Dog(String name) {
this.name = name;
dogCount++; // インスタンスが作られるたびに増える
}
}
Dog dog1 = new Dog("ポチ");
Dog dog2 = new Dog("タロウ");
System.out.println(Dog.dogCount); // 2
dogCountはどのインスタンスからアクセスしても同じ値を参照します。
staticメンバーには、インスタンス変数名.dogCountではなくクラス名.dogCountでアクセスするのが基本ルールです。
手順3:staticメソッドの定義
public class MathUtil {
public static int square(int n) {
return n * n;
}
}
int result = MathUtil.square(5); // インスタンスを作らずに呼び出せる
System.out.println(result); // 25
MathUtil.square(5)のように、インスタンスを作らずクラス名から直接呼び出せるのがstaticメソッドの特徴です。
つまずきやすい設定・注意点
staticメソッドの中からは、インスタンスメンバー(staticでない変数・メソッド)を直接使うことはできません。
これは、staticメソッドが呼ばれる時点で、どのインスタンスの値を使えばいいか特定できないためです。
よくあるつまずきポイント・エラー対処
staticメソッドからインスタンス変数にアクセスしてエラーになる
初心者が実際によくつまずくのが、mainメソッド(static)から、インスタンス変数を直接使おうとするケースです。
❌Before
public class Sample {
int value = 10; // インスタンス変数
public static void main(String[] args) {
System.out.println(value); // コンパイルエラー
}
}
このコードをコンパイルすると、次のエラーが出ます。
error: non-static variable value cannot be referenced from a static context
私も最初にこのエラーを見たとき、「なぜ同じクラスの中なのにアクセスできないのか」と混乱しました。mainはstaticなので、インスタンスが1つも存在しない状態でも実行されます。
インスタンスが存在しないのに、インスタンスごとに異なるvalueを参照することはできない、というのがエラーの理由です。
✅After
インスタンス変数を使いたい場合は、mainの中で明示的にインスタンスを作成してからアクセスします。
public class Sample {
int value = 10;
public static void main(String[] args) {
Sample sample = new Sample(); // インスタンスを作る
System.out.println(sample.value); // 10
}
}
staticフィールドを意図せず共有してしまう
staticフィールドは全インスタンスで共有されるため、「インスタンスごとに別々の値を持たせたかったのに、staticを付けてしまい値が混ざる」というミスも起こりがちです。
public class ShoppingCart {
static int itemCount = 0; // 本来はインスタンスごとに持たせたい値
public void addItem() {
itemCount++;
}
}
ShoppingCart cartA = new ShoppingCart();
ShoppingCart cartB = new ShoppingCart();
cartA.addItem();
cartB.addItem();
System.out.println(cartA.itemCount); // 2(cartAだけの数のつもりが混ざっている)
「このデータはインスタンスごとに独立させたいか、全体で共有したいか」を意識してstaticの有無を判断することが重要です。
応用・一歩先の使い方
static importとユーティリティクラス
MathUtilのように、インスタンス化せず使うメソッドだけを集めたクラスはユーティリティクラスと呼ばれ、実務でも頻繁に登場します。Math.max()やCollections.sort()など、Java標準ライブラリのユーティリティ系クラスの多くもこの設計です。
また、import static java.lang.Math.max;のように書くstatic importを使うと、Math.max(a, b)をmax(a, b)のようにクラス名を省略して呼び出すこともできます。
まとめ
この記事のポイント
- staticはクラスに1つだけ存在し、全インスタンスで共有されるメンバーを表す
- インスタンスメンバーは
newするたびに別々に作られる - staticメソッドの中からインスタンスメンバーへ直接アクセスするとコンパイルエラーになる
- 「インスタンスごとに独立させたいか、全体で共有したいか」を基準にstaticの有無を判断する
次に読むべき記事
- 変更できない値を扱うfinalキーワードの使い方は、次の記事で解説します
- クラスとオブジェクトの基本をまだ確認していない方は、次章の記事もあわせてご覧ください
タグ: Java, 初心者向け, 基本文法