はじめに
この記事はMicroAd Advent Calendar 2017の18日目の記事です。
普段そこまで意識することがないのか、あるのか、
今語らないと今後語ることはないでしょう、java における static のイマサラな話をしてみようと思います。
ゴール
たかが static されど static、
簡単なクイズを書きながら、java における static なモノをこそこそと、今一度簡単に整理してみること。
static フィールド
そもそも static は 「静的」 を意味します。こそこそ。
「動的ではない」ということで、そのフィールドはいくらインスタンスをたくさん生成したとしても、クラスにたった1つです。
複数のインスタンスの間で共有されつづける情報、共有資源にして欲しいフィールドに static をつけます。クラス名.フィールド名
で呼びます。インスタンスを生成して呼び出しても同じモノにアクセスできます。
定数定義の際にfinal static
で宣言することで、new する度に同じ値がインスタンスに複製されることが防止され、メモリの節約に役立ちます。
public final static String KEY = "happy-toilet";
どこにいても呼び出すことができるハッピートイレの固定値です。
static メソッド(クラスメソッド)
static メソッドは、そのものがクラスのインスタンスではなく、クラスに属します。
呼び出しはクラス名.メソッド名
、関数型インターフェースのメソッド参照の場合はクラス名::メソッド名
で直接呼び出しができます。
static フィールドとともに、静的メンバと呼ばれます。
static が付かない一般のメソッドは非 static メソッド、インスタンスメソッドと呼びます。
以下は java8 以降の、関数型インターフェースのメソッド参照における static メソッドとインスタンスメソッドの呼び出しの違いです。
public class MethodReference {
public static void main(String[] args) {
// static メソッド
wakeUp(MethodReference::printWakeUp);
System.out.print(" and ");
// インスタンスメソッド
MethodReference mr = new MethodReference();
mr.goToilet(mr::printGoToilet);
}
interface Human {
void doSomething();
}
static void wakeUp(Human human) {
human.doSomething();
}
static void printWakeUp() {
String wakeUp = "wake up";
System.out.print(wakeUp);
}
void goToilet(Human human) {
human.doSomething();
}
void printGoToilet() {
String toilet = "happy toilet";
System.out.print(toilet);
}
}
結果は wake up and happy toilet です。
オーバーライドできない
public class StaticPractice {
public static void main(String[] args) {
Human developer = new Developer();
System.out.println(developer.getMorning());
}
}
class Human {
static String morning = "Happy Toilet";
static String getMorning() {
return morning;
}
}
class Developer extends Human {
static String morning = "Happy Coding";
static String getMorning() {
return morning;
}
}
もちろん結果は Happy Toilet です。
Developer developer = new Developer();
に直すと、一応 Happy Coding はできます。
しかし、ここではオーバーライドできない
を表すためだけにインスタンスを作ってそのインスタンスから呼び出しをしていますが、そもそもの話 static method は
Human.getMorning();
Developer.getMorning();
と、インスタンスを経由せずにクラスそのものからの参照が望ましいです。
実際、上記のコードをそのまま IntelliJ に移してみると
class itself から参照されていない static method
として注意されます.
(Special thanks to @kawamnv さん)
public static void main(String [] args)
メインメソッドが呼び出される時、まだメモリにインスタンスは存在していないため、メインメソッドはそれを含んでいるクラスがインスタンス化されていなくても、実行される必要があるからです。
static クラス(static インナークラス)
クラスの中のクラス、インナークラスには三種類があり、メンバクラス、ローカルクラス、匿名クラスがあります。
static メンバクラスは、メンバクラスの一つで、宣言場所はクラスブロックの中(フィールドとメソッドと同じ位置)です。
しかし厳密には、static メンバクラスはインナークラスとは呼び難く、まったく別のクラスと表現した方が正しいでしょう。
インナークラスを包んでいるクラスを外部クラスと呼ぶと、
外部クラスやそのインスタンスとの関係が比較的薄く、外部クラスの static メンバにもアクセスできます。
他の static なモノ同様、外部クラス名.メンバクラス名
で利用します。
class Outer { // 外部クラス
// メンバクラス➀ static クラス
static class StaticInner {
private Integer in;
public Integer getIn() {
return in;
}
public void setIn(Integer in) {
this.in = in;
}
}
// メンバクラス➁ 厳密的なメンバクラス
class Inner {
private String str;
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
}
}
無関係なクラスかつメインメソッドでの呼び出しは以下の通りです。
class Execute {
public static void main(String[] args) {
// static クラス
Outer.StaticInner staticInner = new Outer.StaticInner();
// 厳密的なメンバクラス
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
}
}
上記の例に含めてみましたが、厳密なメンバクラスは、外部クラスから生み出される個々のインスタンスと強い結びつきを持つため、外部クラスのインスタンスがなければ new ができません。static なモノではないため、非 static メンバにもアクセスが許容されます。
static イニシャライザ
static {
// 処理
}
static イニシャライザは、クラスロード(.class ファイルがロード)される際に、一度だけ、実行されるブロックです。
あるクラスをインスタンス化する前や、メインメソッドよりも前に呼び出し実行したい処理を記述します。
もちろん記述できるのは static なモノに限ります。
使い道あるかな?と思っていましたが、
public final static String VALUE;
static {
VALUE = makeValue();
}
/**
* 特定条件下で値を動的に生成する
* @return value 生成した値
*/
private static String makeValue() {
// omit
}
先日、ふとしたきっかけでこのような使い方が試せました。
「インスタンス化よりも早いタイミングで、最初に一度だけ新しい値を作っておいて」それを使い回す目的だったので、ちょうどよかったと思います。
コンストラクタと static イニシャライザ
class Something {
void printA() {
System.out.print("t");
}
void printB() {
System.out.print("e");
}
Something () {
System.out.print("l");
printB();
}
static {
System.out.print("i");
}
}
class StaticTest {
static {
System.out.print("T");
}
public static void main(String [] args) {
System.out.print("o");
new Something().printA();
}
}
結果は Toilet です。
static イニシャライザは、new(インスタンス化) によってようやく呼び出されるコンストラクタよりも、早い段階で呼ばれることがわかります。
static import 宣言
java5 以降に導入されました。
以下は前回の投稿の制約アノテーション作成時に使用したソースです。
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Target({FIELD})
@Retention(RUNTIME)
@Constraint(validatedBy = {CustomSizeValidator.class})
public @interface CustomSize {
// omit
}
実際ElementType
とRetentionPolicy
は長いパッケージ名を持つ enum なので、上記のようにすっきりと記述したりします。
外部クラスの static メンバでしたら、別に enum ではなくても利用できます。
import static java.lang.System.*;
import static java.lang.Math.*;
おわりまして
朝一のトイレほど、文法の基礎を再確認することはとても大事です(こそこそ)
以上