はじめに
Java Silverの学習中に、staticメソッドからアクセスできるもの・できないものが混乱しました。
特に、以下のような問題です。
staticメソッドからstaticフィールドにアクセスできるのか?
staticメソッドからインスタンスフィールドにアクセスできるのか?
インスタンスメソッドからstaticメンバにアクセスできるのか?
この記事では、Java Silver対策として、staticメソッドのアクセスルールを初心者向けに整理します。
結論
staticメソッドから直接アクセスできるのは、基本的に staticメンバ です。
| 呼び出し元 | アクセス先 | 直接アクセス |
|---|---|---|
| staticメソッド | staticフィールド | できる |
| staticメソッド | staticメソッド | できる |
| staticメソッド | インスタンスフィールド | できない |
| staticメソッド | インスタンスメソッド | できない |
| インスタンスメソッド | staticフィールド | できる |
| インスタンスメソッド | staticメソッド | できる |
| インスタンスメソッド | インスタンスフィールド | できる |
| インスタンスメソッド | インスタンスメソッド | できる |
ポイントは、
staticメソッドは、インスタンスが存在しなくても呼び出せる
ということです。
staticメンバとは
staticが付いたフィールドやメソッドを、staticメンバ と呼びます。
class Sample {
static int count = 0;
static void printCount() {
System.out.println(count);
}
}
この場合、countとprintCount()はstaticメンバです。
staticメンバは、インスタンスを作らなくてもクラス名.staticメンバから使えます。
Sample.count = 10;
Sample.printCount();
インスタンスメンバとは
一方、staticが付いていないフィールドやメソッドは、インスタンスメンバ です。
class Sample {
int num = 10;
void printNum() {
System.out.println(num);
}
}
numやprintNum()は、インスタンスを作って初めて使えます。
Sample s = new Sample();
s.printNum();
staticメソッドからインスタンスフィールドにアクセスできない理由
次のコードを見てください。
class Sample {
int num = 10;
static void printNum() {
System.out.println(num); // コンパイルエラー
}
}
これはコンパイルエラーになります。
理由は、numがインスタンスフィールドだからです。
インスタンスフィールドは、インスタンスごとに値を持ちます。
Sample s1 = new Sample();
Sample s2 = new Sample();
s1.num = 10;
s2.num = 20;
このとき、numの値はインスタンスごとに違います。
| インスタンス | numの値 |
|---|---|
| s1 | 10 |
| s2 | 20 |
では、staticメソッドから単にnumと書いた場合、Javaはこう思います。
「どのインスタンスのnumを使えばいいの?」
staticメソッドはインスタンスなしでも呼び出せます。
Sample.printNum();
この時点では、s1もs2も指定されていません。
そのため、staticメソッドからインスタンスフィールドを直接使うことはできません。
staticメソッドからインスタンスメソッドも直接呼び出せない
インスタンスメソッドも、インスタンスがないと呼び出せません。
class Sample {
void hello() {
System.out.println("hello");
}
static void test() {
hello(); // コンパイルエラー
}
}
hello()はインスタンスメソッドです。
そのため、staticメソッドから直接呼び出そうとすると、
「どのインスタンスのhello()を呼び出すの?」
となってしまいます。
staticメソッドからインスタンスメンバを使いたい場合
staticメソッドからでも、インスタンスを作ればインスタンスメンバを使えます。
class Sample {
int num = 10;
void printNum() {
System.out.println(num);
}
static void test() {
Sample s = new Sample();
System.out.println(s.num);
s.printNum();
}
}
このように、s.numやs.printNum()のように、どのインスタンスを使うのかを明示すればOKです。
つまり、ダメなのは
System.out.println(num);
printNum();
のように、インスタンスを指定せずに直接使うことです。
staticメソッドからstaticメンバにはアクセスできる
staticメンバ同士であれば、インスタンスがなくても使えます。
class Sample {
static int count = 0;
static void printCount() {
System.out.println(count);
}
}
countもprintCount()もstaticメンバなので、問題ありません。
Sample.printCount();
インスタンスを作らなくても呼び出せます。
インスタンスメソッドからstaticメンバにはアクセスできる
次に、インスタンスメソッドからstaticフィールドやstaticメソッドにアクセスできるかを見てみます。
class Sample {
static int count = 0;
void increment() {
count++;
}
}
これは問題ありません。
インスタンスメソッドは、インスタンスが存在している状態で呼び出されます。
Sample s = new Sample();
s.increment();
このとき、staticフィールドcountはクラスに属しているため、インスタンスメソッドからもアクセスできます。
練習問題
問題
次の中から、コンパイルエラーになるものを選びなさい。
A. インスタンスを作成せずに、クラス名を使ってstaticフィールドを参照する。
B. インスタンスメソッドから、同じクラスのstaticフィールドの値を書き換える。
C. staticメソッドから、同じクラスのstaticメソッドを呼び出す。
D. staticメソッドから、同じクラスのインスタンスメソッドを呼び出す。
答え
D
解説
選択肢Aについて
選択肢Aは、以下の内容でした。
A. インスタンスを作成せずに、クラス名を使ってstaticフィールドを参照する。
これはコンパイルエラーになりません。
staticフィールドはクラスに属するため、インスタンスを作らなくてもクラス名を使って参照できます。
class Sample {
static int count = 0;
}
class Main {
public static void main(String[] args) {
System.out.println(Sample.count);
}
}
Sample.countのように、
クラス名.staticフィールド名
の形でアクセスできます。
むしろ、staticフィールドはこのようにクラス名を使ってアクセスするのが分かりやすいです。
インスタンス経由でもアクセス自体はできますが、Java Silver対策としては、staticメンバはクラスに属するものだと理解しておくのが大切です。
Sample s = new Sample();
System.out.println(s.count); // コンパイルは通るが、staticフィールドなのでクラス名で参照する方が分かりやすい
選択肢Bについて
選択肢Bは、以下の内容でした。
B. インスタンスメソッドから、同じクラスのstaticフィールドの値を書き換える。
これはコンパイルエラーになりません。
理由は、staticフィールドはクラスに属するフィールドであり、インスタンスが存在しているかどうかに関係なく使えるからです。
class Sample {
static int count = 0;
void increment() {
count++;
}
}
increment()はstaticが付いていないため、インスタンスメソッドです。
インスタンスメソッドは、インスタンスを作ってから呼び出します。
Sample s = new Sample();
s.increment();
このとき、increment()の中からstaticフィールドであるcountにアクセスしていますが、これは問題ありません。
countはクラスに属するフィールドなので、インスタンスメソッドからもアクセスできます。
イメージとしては、以下のような関係です。
クラス Sample
└─ staticフィールド count
インスタンス s
└─ increment() から count にアクセスできる
つまり、
インスタンスメソッドからstaticメンバにはアクセスできる
ということです。
選択肢Cについて
選択肢Cは、以下の内容でした。
C. staticメソッドから、同じクラスのstaticメソッドを呼び出す。
これはコンパイルエラーになりません。
staticメソッドからstaticメソッドは直接呼び出せます。
class Sample {
static void methodA() {
methodB();
}
static void methodB() {
System.out.println("methodB");
}
}
methodA()もmethodB()もstaticメソッドです。
どちらもインスタンスを作らなくても使えるため、staticメソッドから直接呼び出しても問題ありません。
Sample.methodA();
このように、staticメンバ同士であれば、インスタンスがなくてもアクセスできます。
選択肢Dについて
staticメソッドから直接インスタンスメソッドを呼び出すとコンパイルエラーが発生します。
class Sample {
void hello() {
System.out.println("hello");
}
static void test() {
hello(); // コンパイルエラー
}
}
エラー: staticでないメソッド hello()をstaticコンテキストから参照することはできません
hello(); // コンパイルエラー
^
このコンパイルエラーを解消するには以下のように、インスタンスを生成し、そのインスタンスの参照を使って間接的にアクセスします。
class Sample {
void hello() {
System.out.println("hello");
}
static void test() {
Sample s = new Sample();
s.hello();
}
}
まとめ
Java Silverでは、staticに関する問題がよく出てきます。
迷ったときは、
「今、インスタンスが存在している前提なのか?」
を考えると整理しやすいです。
特に大事なのは、以下の一文です。
staticメソッドからインスタンスメンバには直接アクセスできない
ただし、インスタンスを生成して s.num や s.hello() のように指定すれば、staticメソッドの中からでもインスタンスメンバを使えます。
このルールを押さえておくと、static関連の問題でかなり迷いにくくなります。
参考文献
この記事は以下を参考に記載しました。
徹底攻略Java SE 17 Silver問題集